// ==UserScript==
// @name         生财有术-志愿者后台辅助插件（23年12月航海实战）
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  提供表格视图等辅助功能
// @author       MD(mdxue00)
// @match        https://scys.com/activity/task/volunteer?*
// @icon         https://scys.com/favicon.ico
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-end
// @require      https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      http://html2canvas.hertzen.com/dist/html2canvas.min.js
// @updateURL    https://gitee.com/mondyxue/scys.user.js/raw/master/202312.volunteer.scys.user.js
// ==/UserScript==

(function (window) {
    'use strict';

    const MD = (() => {

        var _values = localStorage.getItem(`MD.${window.location.href}`);
        if (_values === null) {
            _values = {}
        } else {
            _values = JSON.parse(_values)
        }
        const _getLocalStorageItem = (storageName, key, defVal) => {
            if (_values[storageName]) {
                var value = _values[storageName][key]
                if (value !== undefined) {
                    return value
                }
            }
            return defVal
        }
        const _setLocalStorageItem = (storageName, valueKey, value, force = true) => {
            if (value === undefined) {
                if (valueKey === undefined) {
                    if (storageName === undefined) {
                        _values = {}
                    } else {
                        if (_values[storageName] === undefined) {
                            return
                        }
                        delete _values[storageName]
                    }
                } else {
                    if (_values[storageName] === undefined || _values[storageName][valueKey] === undefined) {
                        return
                    }
                    delete _values[storageName][valueKey]
                }
            } else {
                if (_values[storageName] === undefined) {
                    _values[storageName] = {}
                }
                _values[storageName][valueKey] = value
            }
            force && localStorage.setItem(`MD.${window.location.href}`, JSON.stringify(_values));
        }

        class Storage {

            static global = new Storage('global')

            static remove(storageName) {
                _setLocalStorageItem(storageName)
            }

            constructor(storageName) {
                this.storageName = storageName
            }
            set(key, value, force) {
                _setLocalStorageItem(this.storageName, key, value, force)
            }
            get(key, defVal) {
                return _getLocalStorageItem(this.storageName, key, defVal)
            }
            delete(key) {
                _setLocalStorageItem(this.storageName, key)
            }
            clear() {
                _setLocalStorageItem(this.storageName)
            }
        }

        class ListManager {
            constructor({ key, storageName, managerListener, onInit, items = [], cachingEnabled = { items: false, sortOptions: false, sortedItemKeys: false, newItemKeys: false } }) {

                this.key = key
                this._managerListeners = []

                if (!(items instanceof Array)) {
                    items = Array.from(items)
                }

                storageName = storageName || key
                this._cache = new function () {
                    const _storage = new Storage(storageName)
                    this.get = (key, defValue) => {
                        if (!this[key]) {
                            if (cachingEnabled[key]) {
                                this[key] = _storage.get(key, defValue)
                            } else {
                                this[key] = defValue
                            }
                        }
                        return this[key]
                    }
                    this.set = (key, value, force = true) => {
                        this[key] = value
                        if (cachingEnabled[key]) {
                            _storage.set(key, value, force)
                        }
                        return this[key]
                    }
                    this.clear = () => {
                        _storage.clear()
                    }
                }
                if (managerListener) {
                    this.addManagerListener(managerListener)
                }

                onInit && onInit(this)

                var cacheItems = this._cache.get("items")
                if (!cacheItems || cacheItems.length === 0) {
                    var sortOptions = this._cache.get("sortOptions", [])
                    var sortedItemKeys = this._cache.get("sortedItemKeys", [])
                    this.sortItems({ items, sortOptions, sortedItemKeys, force: items ? false : true })
                }
            }

            clearCache() {
                this._cache.clear()
                this.emitManagerEvent('clearCache', {})
            }

            addManagerListener(listener) {
                this._managerListeners.push(listener)
            }

            emitManagerEvent(event, params) {
                this._managerListeners.forEach((listener) => listener[event] && listener[event](params))
            }

            getItem({ itemKey, itemIndex }) {
                if (itemKey) {
                    return this.getItems().find(item => this.getItemKey(item) === itemKey)
                } else {
                    return this.getItems().find((item, index) => index === itemIndex)
                }
            }

            getItems() {
                return this._cache.get('items')
            }

            getItemIndex(itemKey) {
                return this.getItems().findIndex(item => this.getItemKey(item) === itemKey)
            }

            getItemKey(item) {
                return item.key
            }
            getItemKeys() {
                return this.getItems().map(item => this.getItemKey(item))
            }

            getItemValues({ item, itemKey }) {
                item = item || this.getItem({ itemKey })
                return Object.keys(item).map(valueKey => ({ valueKey, value: item[valueKey] }))
            }
            getItemValue({ item, itemKey, valueKey }) {
                item = item || this.getItem({ itemKey })
                return item ? item[valueKey] : undefined
            }

            updateItemKey({ item, itemKey, newItemKey }) {

                var newItemKeys = this._cache.get('newItemKeys', [])
                if (!newItemKeys.includes(itemKey)) {
                    return itemKey
                }

                itemKey = itemKey || this.getItemKey(item)
                item = item || this.getItem({ itemKey })
                while (this.getItemKeys().includes(newItemKey)) {
                    newItemKey += '1'
                }

                this.onUpdateItemKey({ item, newItemKey })

                newItemKeys = Utils.Array.replaceItem(newItemKeys, this.getItemIndex(itemKey), newItemKey)
                this._cache.set('newItemKeys', newItemKeys)

                var sortedItemKeys = this._cache.get('sortedItemKeys')
                sortedItemKeys = Utils.Array.replaceItem(sortedItemKeys, this.getItemIndex(itemKey), newItemKey)
                this._cache.set('sortedItemKeys', sortedItemKeys)

                this.emitManagerEvent('updateItemKey', { item, itemKey, newItemKey })

                return newItemKey
            }
            onUpdateItemKey({ item, newItemKey }) {
                item.key = newItemKey
            }

            updateItemValue({ item, itemKey, valueKey, value }) {

                const items = this.getItems()

                item = item || this.getItem({ itemKey })
                if (item) {
                    item[valueKey] = value
                    this._cache.set('items', this.getItems())
                } else {
                    item = { key: itemKey }
                    item[valueKey] = value
                    this.insertItem({ item })
                }

                this.emitManagerEvent('updateItemValue', { item, itemKey, valueKey, value })
            }
            updateItem({ item, itemKey, values }) {

                item = item || this.getItem({ itemKey })
                if (item) {
                    var items = this.getItems()
                    if (values) {
                        values.forEach(({ valueKey, value }) => {
                            item[valueKey] = value
                        })
                    }
                    Utils.Array.replaceItem(items, items.indexOf(item), item)
                    this._cache.set('items', items)
                } else {
                    item = { key: itemKey }
                    values.forEach(({ valueKey, value }) => {
                        item[valueKey] = value
                    })
                    this.insertItem({ item })
                }

                this.emitManagerEvent('updateItem', { item, itemKey, values })
            }
            updateItems(itemValues) {

                itemValues.forEach(({ item, itemKey, values }) => this.updateItem({ item, itemKey, values }))

                this.emitManagerEvent('updateItems', itemValues)
            }

            insertItem({ item, beforeItemKey, beforeItem }) {

                var itemKey = this.getItemKey(item)
                var itemKeys = this.getItemKeys()
                var newItemKeys = this._cache.get('newItemKeys', [])
                while (itemKeys.includes(itemKey) || newItemKeys.includes(itemKey)) {
                    itemKey += '1'
                }
                item.key = itemKey

                var items = this.onInsertItem({ item, beforeItem: beforeItem || this.getItem({ itemKey: beforeItemKey }) })
                this._cache.set('items', items)
                this._cache.set('sortedItemKeys', this.getItemKeys())

                newItemKeys.push(itemKey)
                this._cache.set('newItemKeys', newItemKeys)

                this.emitManagerEvent('insertItem', { item, beforeItemKey, beforeItem })
            }
            onInsertItem({ item, beforeItem }) {
                return Utils.Array.insertItem(this.getItems(), item, beforeItem)
            }

            copyItem({ item, itemKey, beforeItemKey, beforeItem }) {
                this.insertItem({ item: item || this.getItem({ itemKey }), beforeItem, beforeItemKey })
                this.emitManagerEvent('copyItem', { item, itemKey, beforeItemKey, beforeItem })
            }

            removeItem({ itemKey, force }) {

                var newItemKeys = this._cache.get('newItemKeys', [])
                if (newItemKeys.includes(itemKey)) {
                    newItemKeys = Utils.Array.removeItem(newItemKeys, itemKey)
                    this._cache.set('newItemKeys', newItemKeys)
                } else if (force === false) {
                    return false
                }

                var items = this.onRemoveItem({ itemKey })
                this._cache.set('items', items)
                this._cache.set('sortedItemKeys', this.getItemKeys())

                this.emitManagerEvent('removeItem', { itemKey })
                return true
            }

            onRemoveItem({ itemKey }) {
                return Utils.Array.removeItem(this.getItems(), this.getItem({ itemKey }))
            }

            removeAllItems() {
                var items = this.getItems()
                if (items.length === 0) {
                    return false
                }
                items = []
                this._cache.set('items', items)
                return true
            }

            moveItem({ itemKey, direction, condition }) {
                this.sortItems({ sortedItemKeys: Utils.Array.moveItem(this.getItemKeys(), this.getItemIndex(itemKey), direction, condition) })

                this.emitManagerEvent('moveItem', { itemKey, direction, condition })
            }

            sortItems({ items, sortOptions, sortedItemKeys, force = true }) {

                items = items || this.getItems()

                if (sortOptions && sortOptions.length > 0) {

                    this._cache.set('sortOptions', sortOptions)
                    this._cache.set('sortedItemKeys', [])

                    items = items.sort((itemA, itemB) => {
                        for (let i = 0; i < sortOptions.length; i++) {
                            const { valueKey, orderBy } = sortOptions[i];
                            var valueA = this.getItemValue({ item: itemA, valueKey });
                            if (valueA === undefined) { valueA = '' }
                            var valueB = this.getItemValue({ item: itemB, valueKey });
                            if (valueB === undefined) { valueB = '' }
                            if (orderBy === 'esc') {
                                if (valueA < valueB) return -1;
                                if (valueA > valueB) return 1;
                            } else if (orderBy === 'desc') {
                                if (valueA > valueB) return -1;
                                if (valueA < valueB) return 1;
                            }
                        }
                        return 0
                    })

                } else if (sortedItemKeys && sortedItemKeys.length > 0) {

                    this._cache.set('sortOptions', [])
                    this._cache.set('sortedItemKeys', sortedItemKeys)

                    items = items.sort((itemA, itemB) => {
                        itemA = sortedItemKeys.indexOf(this.getItemKey(itemA))
                        itemB = sortedItemKeys.indexOf(this.getItemKey(itemB))
                        return itemA < itemB ? -1 : 1;
                    })

                } else if (items) {
                    sortedItemKeys = items.map(item => this.getItemKey(item))
                }

                items = this.onSortItems(items)
                this._cache.set('items', items, force)

                this.emitManagerEvent('sortItems', { items, sortOptions, sortedItemKeys })
            }
            onSortItems(items) {
                return items
            }

            hasItem({ itemKey, item }) {
                itemKey = itemKey || this.getItemKey(item)
                if (this.getItemKeys().find(key => key === itemKey)) {
                    return true
                } else {
                    return false
                }
            }
            isNewItem({ itemKey, item }) {
                itemKey = itemKey || this.getItemKey(item)
                return this._cache.get('newItemKeys', []).includes(itemKey)
            }
        }

        class ObjectDataListManager extends ListManager {
            constructor({ key, storageName, managerListener, onInit, items = [], cachingEnabled = { items: false, sortOptions: false, sortedItemKeys: false, newItemKeys: false } }) {
                super({ key, storageName, managerListener, onInit, items, cachingEnabled })
            }
        }

        class ElementListManager extends ListManager {
            constructor({ key, managerListener, storageName, onInit, parentElement, cachingEnabled = { sortOptions: false, sortedItemKeys: false, hiddenItemKeys: false, newItemKeys: false } }) {
                super({
                    key, storageName, managerListener,
                    onInit: (self) => {
                        self.parentElement = parentElement
                        onInit && onInit(self)
                    },
                    items: parentElement.children, cachingEnabled
                })

                var hiddenItemKeys = this._cache.get('hiddenItemKeys', [])
                this.getItems().forEach(item => {
                    var itemKey = this.getItemKey(item)
                    if (hiddenItemKeys.includes(itemKey)) {
                        this.hideItem({ itemKey, item })
                    } else {
                        this.showItem({ itemKey, item })
                    }
                })
            }

            onUpdateItemKey({ item, newItemKey }) {

                var itemKey = this.getItemKey(item)

                var hiddenItemKeys = this._cache.get('hiddenItemKeys')
                if (hiddenItemKeys.includes(itemKey)) {
                    hiddenItemKeys = Utils.Array.replaceItem(hiddenItemKeys, this.getItemIndex(itemKey), newItemKey)
                    this._cache.set('hiddenItemKeys', hiddenItemKeys)
                }

                return super.onUpdateItemKey({ item, newItemKey })
            }

            onInsertItem({ item, beforeItem }) {
                if (beforeItem) {
                    this.parentElement.insertBefore(item, beforeItem)
                } else {
                    this.parentElement.appendChild(beforeItem)
                }
                return super.onInsertItem({ item, beforeItem })
            }

            onRemoveItem({ itemKey }) {
                this.parentElement.removeChild(this.getItem({ itemKey }))
                return super.onRemoveItem({ itemKey })
            }

            onSortItems(items) {
                items.forEach(item => {
                    this.parentElement.appendChild(item)
                })
                return items
            }

            moveItem({ itemKey, direction }) {
                super.moveItem({ itemKey, direction, condition: (itemKey) => !this.isItemHidden({ itemKey }) })
            }

            showItem({ itemKey, item, force = true }) {

                item = item || this.getItem({ itemKey })
                UI.show(item)
                if (!force) {
                    this.emitManagerEvent('showItem', { itemKey, item, force })
                    return
                }

                itemKey = itemKey || this.getItemKey(item)
                var hiddenItemKeys = this._cache.get('hiddenItemKeys')
                if (hiddenItemKeys.includes(itemKey)) {
                    hiddenItemKeys = Utils.Array.removeItem(hiddenItemKeys, itemKey)
                    this._cache.set('hiddenItemKeys', hiddenItemKeys)
                }
                this.emitManagerEvent('showItem', { itemKey, item, force })
            }
            hideItem({ itemKey, item, force = true }) {

                item = item || this.getItem({ itemKey })
                UI.hide(item)
                if (!force) {
                    this.emitManagerEvent('hideItem', { itemKey, item, force })
                    return
                }

                itemKey = itemKey || this.getItemKey(item)
                var hiddenItemKeys = this._cache.get('hiddenItemKeys')
                if (!hiddenItemKeys.includes(itemKey)) {
                    hiddenItemKeys.push(itemKey)
                    this._cache.set('hiddenItemKeys', hiddenItemKeys)
                }

                this.emitManagerEvent('hideItem', { itemKey, item, force })
            }

            isItemHidden({ itemKey, item }) {
                return UI.isHidden(item || this.getItem({ itemKey }))
            }

            getVisibleItems() {
                return this.getItems().filter(item => !UI.isHidden(item))
            }
            getHiddenItems() {
                return this.getItems().filter(item => UI.isHidden(item))
            }

            show() {
                UI.show(this.parentElement)
                this.emitManagerEvent('show', {})
            }
            hide() {
                UI.hide(this.parentElement)
                this.emitManagerEvent('hide', {})
            }
        }

        class TableManager {

            constructor({ key, onInit, reload, styles, tableElement, datas, columns, cachingEnabled = { rows: true, columns: true, filters: true } }) {

                this.reload = reload

                this.key = `${key}.TableManager`
                this._cache = new function () {
                    const _storage = new Storage(key)
                    this.get = (key, defValue) => {
                        if (!this[key]) {
                            if (cachingEnabled[key]) {
                                this[key] = _storage.get(key, defValue)
                            } else {
                                this[key] = defValue
                            }
                        }
                        return this[key]
                    }
                    this.set = (key, value) => {
                        this[key] = value
                        if (cachingEnabled[key]) {
                            _storage.set(key, value)
                        }
                        return this[key]
                    }
                    this.clear = () => {
                        _storage.clear()
                    }
                }

                onInit && onInit(this)

                this.rowFiltersManager = new ObjectDataListManager({ key: `${key}.rowFiltersManager`, items: [], cachingEnabled: { items: cachingEnabled.filters } })
                this.sortOptionsManager = new ObjectDataListManager({ key: `${key}.sortOptionsManager`, items: [], cachingEnabled: { items: cachingEnabled.rows } })
                this.localValuesManager = new ObjectDataListManager({ key: `${key}.localValuesManager`, items: [], cachingEnabled: { items: true } })
                this.datasManager = new ObjectDataListManager({ key: `${key}.datasManager`, items: datas, cachingEnabled: { /*sortedItemKeys: cachingEnabled.rows, sortOptions: cachingEnabled.rows, newItemKeys: cachingEnabled.rows*/ } })
                this.columnsManager = new ObjectDataListManager({ key: `${key}.columnsManager`, items: [{ key: '行号', editable: false }].concat(columns), cachingEnabled: { items: cachingEnabled.columns, sortedItemKeys: cachingEnabled.columns, newItemKeys: cachingEnabled.columns } })

                tableElement.appendChild(document.createElement('thead'))
                tableElement.appendChild(document.createElement('tbody'))
                this.tableElement = tableElement

                const bodyElement = tableElement.tBodies[0]
                this.bodyRowCellsManagers = new ObjectDataListManager({
                    key: `${key}.bodyRowCellsManagers`,
                    items: this.datasManager.getItemKeys().map(rowKey => {
                        const manager = new ElementListManager({ key: rowKey, parentElement: this._createBodyRowELement({ rowKey }) })
                        bodyElement.appendChild(manager.parentElement)
                        return manager
                    })
                })

                const headElement = tableElement.tHead
                this.headRowCellsManager = new ElementListManager({
                    key: '表头',
                    storageName: `${key}.headRowCellsManager`,
                    parentElement: this._createHeadRowELement({ rowKey: '表头' }),
                    cachingEnabled: { sortedItemKeys: true, hiddenItemKeys: true, newItemKeys: true },
                    managerListener: {
                        showItem: ({ itemKey, force }) => {
                            this.bodyRowCellsManagers.getItems().forEach(manager => {
                                manager.showItem({ itemKey, force })
                            })
                        },
                        hideItem: ({ itemKey, force }) => {
                            this.bodyRowCellsManagers.getItems().forEach(manager => {
                                manager.hideItem({ itemKey, force })
                            })
                        },
                        sortItems: ({ items, sortedItemKeys }) => {
                            this.bodyRowCellsManagers.getItems().forEach(manager => {
                                manager.sortItems({ sortedItemKeys })
                            })
                        },
                        insertItem: ({ item, beforeItem, beforeItemKey }) => {
                            this.columnsManager.insertItem({ item: { key: this.headRowCellsManager.getItemKey(item), beforeItemKey } })
                            this.bodyRowCellsManagers.getItems().forEach(manager => {
                                manager.insertItem({ item: this._createBodyCellElement({ rowKey: manager.key, columnKey: this.headRowCellsManager.getItemKey(item) }), beforeItemKey, beforeItem })
                            })
                        },
                        removeItem: ({ itemKey }) => {
                            this.columnsManager.removeItem({ itemKey })
                            this.bodyRowCellsManagers.getItems().forEach(manager => {
                                this.localValuesManager.removeItem({ itemKey: `${manager.key}&${itemKey}` })
                                manager.removeItem({ itemKey })
                            })
                        }
                    }
                })
                headElement.appendChild(this.headRowCellsManager.parentElement)

                this.bodyRowsManager = new ElementListManager({
                    storageName: `${key}.bodyRowsManager`, parentElement: bodyElement, cachingEnabled: { sortedItemKeys: true, hiddenItemKeys: true },
                    managerListener: {
                        sortItems: ({ items, sortedItemKeys }) => {
                            this.bodyRowCellsManagers.sortItems({ sortedItemKeys })
                        }
                    }
                })

                this.initStyles(styles)

                this.updateTable()

                this.updateSortedRows()
                this.updateFilterRows()

                // this.rowFiltersManager.getItems().forEach(rowFilter => {
                //     this.addFilter(rowFilter)
                // })

            }

            clearCache() {
                this._cache.clear()
                this.columnsManager.clearCache()
                this.localValuesManager.clearCache()
                this.rowFiltersManager.clearCache()
                this.sortOptionsManager.clearCache()
            }

            _createHeadRowELement({ rowKey }) {
                var rowElement = document.createElement('tr')
                rowElement.key = rowKey
                // rowElement.rowNumberCellElement = this._createHeadCellElement(rowKey, '行号')
                // rowElement.appendChild(rowElement.rowNumberCellElement)
                rowElement.style.position = 'sticky'
                rowElement.style.top = '0'
                rowElement.style.zIndex = '2'
                this.columnsManager.getItemKeys().forEach(columnKey => {
                    rowElement.appendChild(this._createHeadCellElement({ rowKey, columnKey }))
                })
                return rowElement
            }

            _createHeadCellElement({ rowKey, columnKey }) {
                var cellElement = document.createElement('th')
                cellElement.key = columnKey
                cellElement.style.background = '#fbfbfb'
                if (columnKey === '行号') {
                    cellElement.style.position = 'sticky'
                    cellElement.style.left = 0
                    cellElement.style.zIndex = 1
                    UI.addEventListeners(cellElement, {
                        onClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onHeadCellClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                            const text = this.bodyRowsManager.getVisibleItems().map(rowElement => {
                                return this.bodyRowCellsManagers.getItem({ itemKey: this.bodyRowsManager.getItemKey(rowElement) }).getVisibleItems().map(cellElement => cellElement.cellValue).join('\t')
                            }).join('\n')
                            UI.copyText(text)
                        },
                        onDblClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onHeadCellDblClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                        },
                        onRightClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onHeadCellRightClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                            UI.createHoverMenu({
                                element: cellElement,
                                isShowNow: true,
                                buttons: Array.of(
                                    ['取消所有筛选', () => {
                                        this.removeAllFilters()
                                    }],
                                    ['取消所有排序', () => {
                                        this.removeAllSortOptions()
                                    }],
                                    ['显示所有列', () => {
                                        this.headRowCellsManager.getItemKeys().forEach(itemKey => {
                                            this.headRowCellsManager.showItem({ itemKey, force: true })
                                        })
                                    }],
                                    ['重置表格数据', () => {
                                        this.localValuesManager.removeAllItems()
                                        this.updateTable()
                                    }],
                                    ['重置表格列设置', () => {
                                        this.columnsManager.removeAllItems()
                                        window.location.reload()
                                    }]
                                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                            })
                        }
                    })
                } else {
                    UI.addEventListeners(cellElement, {
                        onClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onHeadCellClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                            const text = this.bodyRowsManager.getVisibleItems().map(rowElement => {
                                return this.bodyRowCellsManagers.getItem({ itemKey: this.bodyRowsManager.getItemKey(rowElement) }).getItem({ itemKey: columnKey }).cellValue
                            }).join('\n')
                            UI.copyText(text)
                        },
                        onDblClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onHeadCellDblClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                            if (!this.columnsManager.isNewItem({ itemKey: columnKey })) {
                                return
                            }
                            UI.createHoverEditor({
                                element: cellElement, value: this.localValuesManager.getItemValue({ itemKey: `${rowKey}&${columnKey}`, valueKey: 'value' }) || columnKey, onSave: value => {
                                    if (this.columnsManager.hasItem({ itemKey: value }) || this.columnsManager.getItemKeys().find(itemKey => value === this.localValuesManager.getItemValue({ itemKey: `${rowKey}&${itemKey}`, valueKey: 'value' }))) {
                                        UI.showToast('列名已存在')
                                        return false
                                    }
                                    this.localValuesManager.updateItemValue({ itemKey: `${rowKey}&${columnKey}`, valueKey: 'value', value })
                                    this.updateTable({ columnKey })
                                    return true
                                }
                            })
                        },
                        onRightClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onHeadCellRightClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                            UI.createHoverMenu({
                                element: cellElement,
                                isShowNow: true,
                                buttons: Array.of(
                                    ['清除筛选', () => {
                                        this.removeFilter({ columnKey })
                                    }, (btn) => {
                                        btn.hidden = this.rowFiltersManager.getItem({ itemKey: columnKey }) ? false : true
                                    }],
                                    ['左移一列', () => {
                                        this.moveColumn({ columnKey, direction: -1 })
                                    }, (btn) => {
                                        btn.hidden = this.headRowCellsManager.getItemIndex(columnKey) <= 1
                                    }],
                                    ['右移一列', () => {
                                        this.moveColumn({ columnKey, direction: 1 })
                                    }, (btn) => {
                                        btn.hidden = this.headRowCellsManager.getItemIndex(columnKey) === this.headRowCellsManager.getItems().length - 1
                                    }],
                                    ['添加一列', () => {
                                        this.insertColumnBefore({ columnKey, beforeColumnKey: columnKey })
                                    }],
                                    ['隐藏本列', () => {
                                        this.hideColumn({ columnKey })
                                    }, btn => {
                                        btn.innerText = `隐藏 ${this.localValuesManager.getItemValue({ itemKey: `${rowKey}&${columnKey}`, valueKey: 'value' }) || columnKey}`
                                        // btn.hidden = !this.columnsManager.isNewItem({ itemKey: columnKey })
                                    }],
                                    ['删除本列', () => {
                                        this.deleteColumn({ columnKey })
                                    }, btn => {
                                        btn.innerText = `删除 ${this.localValuesManager.getItemValue({ itemKey: `${rowKey}&${columnKey}`, valueKey: 'value' }) || columnKey}`
                                        btn.hidden = !this.columnsManager.isNewItem({ itemKey: columnKey })
                                    }]
                                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                            });
                        }
                    })
                }

                return cellElement
            }

            _createBodyRowELement({ rowKey }) {
                var rowElement = document.createElement('tr')
                rowElement.key = rowKey
                // rowElement.rowNumberCellElement = this._createBodyCellElement(rowKey, '行号')
                // rowElement.appendChild(rowElement.rowNumberCellElement)
                this.columnsManager.getItemKeys().forEach(columnKey => {
                    rowElement.appendChild(this._createBodyCellElement({ rowKey, columnKey }))
                })
                return rowElement
            }

            _createBodyCellElement({ rowKey, columnKey }) {
                var cellElement = document.createElement('td')
                cellElement.key = columnKey
                if (columnKey === '行号') {
                    cellElement.style.background = '#fbfbfb'
                    cellElement.style.position = 'sticky'
                    cellElement.style.left = 0
                    cellElement.style.zIndex = 1
                    UI.addEventListeners(cellElement, {
                        onClick: () => {
                            var text = this.bodyRowCellsManagers.getItem({ itemKey: rowKey }).getVisibleItems().map(cellElement => cellElement.cellValue).join('\t')
                            UI.copyText(text)
                        },
                        onRightClick: () => {
                            UI.createHoverMenu({
                                element: cellElement,
                                isShowNow: true,
                                buttons: Array.of(
                                    ['上移一行', () => {
                                        this.moveRow({ rowKey, direction: -1 })
                                    }],
                                    ['下移一行', () => {
                                        this.moveRow({ rowKey, direction: 1 })
                                    }],
                                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                            })
                        }
                    })
                } else {
                    UI.addEventListeners(cellElement, {
                        onClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onBodyCellClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                            UI.copyText(cellValue)
                        },
                        onDblClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onBodyCellDblClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                            if (this.columnsManager.getItemValue({ itemKey: columnKey, valueKey: 'editable' }) === false) {
                                UI.showToast('该单元格不可编辑')
                                return
                            }
                            UI.createHoverEditor({
                                element: cellElement, value: cellValue,
                                onSave: (value) => {
                                    if (value === '' || value === this.datasManager.getItemValue({ itemKey: rowKey, valueKey: columnKey })) {
                                        this.localValuesManager.removeItem({ itemKey: `${rowKey}&${columnKey}` })
                                    } else {
                                        this.localValuesManager.updateItemValue({ itemKey: `${rowKey}&${columnKey}`, valueKey: 'value', value })
                                    }
                                    this.updateTable({ rowKey, columnKey })
                                    return true
                                }
                            })
                        },
                        onRightClick: () => {
                            const { key: columnKey, cellValue } = cellElement
                            if (this.onBodyCellRightClick({ cellElement, rowKey, columnKey, cellValue })) {
                                return
                            }
                            UI.createHoverMenu({
                                element: cellElement,
                                isShowNow: true,
                                buttons: Array.of(
                                    ['恢复默认值', () => {
                                        this.localValuesManager.removeItem({ itemKey: `${rowKey}&${columnKey}` })
                                        this.updateTable({ rowKey, columnKey })
                                    }, btn => {
                                        btn.hidden = !this.localValuesManager.hasItem({ itemKey: `${rowKey}&${columnKey}` })
                                    }],
                                    ['粘贴', () => {
                                        Utils.Clipboard.getText().then(text => {
                                            if (text === '') {
                                                UI.showToast('剪贴板没有可粘贴文本')
                                                return
                                            }
                                            var rowValues = text.split('\n').map(text => text.split('\t'))
                                            var visibleRowKeys = []
                                            var visibleColumnKeys = []
                                            this.bodyRowsManager.getVisibleItems().forEach(rowElement => {
                                                var visibleRowKey = this.bodyRowsManager.getItemKey(rowElement)
                                                if (this.bodyRowsManager.getItemIndex(visibleRowKey) < this.bodyRowsManager.getItemIndex(rowKey)) {
                                                    return
                                                }
                                                visibleRowKeys.push(visibleRowKey)
                                            })
                                            this.headRowCellsManager.getVisibleItems().forEach(cellElement => {
                                                var visibleColumnKey = this.headRowCellsManager.getItemKey(cellElement)
                                                if (this.headRowCellsManager.getItemIndex(visibleColumnKey) < this.headRowCellsManager.getItemIndex(columnKey)) {
                                                    return
                                                }

                                                visibleColumnKeys.push(visibleColumnKey)
                                            })
                                            rowValues.forEach((values, rowIndex) => {
                                                values.forEach((value, columnIndex) => {
                                                    if (value === '' || this.columnsManager.getItemValue({ itemKey: visibleColumnKeys[columnIndex], valueKey: 'editable' }) === false) {
                                                        return
                                                    }
                                                    value = value.trim()
                                                    this.localValuesManager.updateItemValue({ itemKey: `${visibleRowKeys[rowIndex]}&${visibleColumnKeys[columnIndex]}`, valueKey: 'value', value })
                                                })
                                            })
                                            this.updateTable()
                                        })
                                    }],
                                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                            });
                        }
                    })
                }
                return cellElement
            }

            onHeadCellClick({ cellElement, rowKey, columnKey }) { }
            onHeadCellDblClick({ cellElement, rowKey, columnKey }) { }
            onHeadCellRightClick({ cellElement, rowKey, columnKey }) { }

            onBodyCellClick({ cellElement, rowKey, columnKey }) { }
            onBodyCellDblClick({ cellElement, rowKey, columnKey }) { }
            onBodyCellRightClick({ cellElement, rowKey, columnKey }) { }

            onUpdateHeadCell({ cellElement, rowKey, columnKey, cellValue }) { }
            onUpdateBodyCell({ cellElement, rowKey, columnKey, cellValue }) { }

            initStyles(styles) {
                if (styles) {
                    UI.addStyle(styles)
                }
                UI.addStyle(`
                        #md-table-wrapper{
                            width: 100%;
                            height: 750px;
                            overflow-x: scroll;
                            overflow-y: scroll;
                        }
                        #md-table{
                            table-layout: auto;
                            vertical-align: middle!important;
                            width: fit-content;
                            border-spacing: 0;
                        }
                        #md-table>*>*>* {
                            white-space: nowrap;
                            padding: 0 5px;
                            border: 1px solid #eee;
                            position: relative;
                        }
                        #md-table>*>*>*>.break-line{
                            white-space: pre-line;
                            text-align: left;
                            width: max-content;
                            overflow: hidden;
                        }
                        #md-table>*>*>*:hover {
                            background: #eee;
                            cursor: pointer;
                        }
                        #md-table>thead>tr>th{
                            background:white;
                            height: 33px;
                            font-size: 13px;
                            color: #006659;
                        }
                    `)
            }

            getCellValue({ rowKey, columnKey }) {
                var cellElement = this.getCell({ rowKey, columnKey })
                return cellElement ? cellElement.cellValue : ''
            }
            getCell({ rowKey, columnKey }) {
                return (rowKey === this.headRowCellsManager.key ? this.headRowCellsManager : this.bodyRowCellsManagers.getItem({ itemKey: rowKey })).getItem({ itemKey: columnKey })
            }

            hideRow({ rowKey }) {
                this.bodyRowsManager.hideItem({ itemKey: rowKey, force: true })
                this.updateTable({ columnKey: '行号' })
            }

            showRow({ rowKey }) {
                this.bodyRowsManager.showItem({ itemKey: rowKey, force: true })
                this.updateTable({ columnKey: '行号' })
            }

            hideColumn({ columnKey, force = true }) {
                this.headRowCellsManager.hideItem({ itemKey: columnKey, force })
            }

            showColumn({ columnKey }) {
                this.headRowCellsManager.showItem({ itemKey: columnKey, force: true })
            }

            addFilter = ({ columnKey, cellValues }) => {
                var rowFilter = this.rowFiltersManager.getItem({ itemKey: columnKey })
                if (rowFilter) {
                    rowFilter.cellValues = cellValues
                    this.rowFiltersManager.updateItem({ item: rowFilter })
                } else {
                    this.rowFiltersManager.insertItem({ item: { key: columnKey, columnKey, cellValues } })
                }
                this.updateFilterRows()
            }

            updateFilterRows() {
                var rowFilters = this.rowFiltersManager.getItems()
                if (rowFilters.length > 0) {
                    this.bodyRowCellsManagers.getItems().forEach(manager => {
                        if (rowFilters.find(({ columnKey, cellValues }) => {
                            var cellElement = manager.getItem({ itemKey: columnKey })
                            return cellElement && cellValues.find(cellValue => {
                                return cellElement.cellValue === cellValue
                            }) ? false : true
                        })) {
                            this.bodyRowsManager.hideItem({ itemKey: manager.key, force: false })
                        } else {
                            this.bodyRowsManager.showItem({ itemKey: manager.key, force: false })
                        }
                    })
                } else {
                    this.bodyRowsManager.getItemKeys().forEach(itemKey => {
                        this.bodyRowsManager.showItem({ itemKey, force: false })
                    })
                }
                this.updateTable({ measureColumnWidth: false, rowKey: '表头', columnKey: '行号' })
            }

            addFilterValue = ({ columnKey, cellValue }) => {
                var rowFilter = this.rowFiltersManager.getItem({ itemKey: columnKey })
                if (rowFilter) {
                    if (!rowFilter.cellValues.includes(cellValue)) {
                        rowFilter.cellValues.push(cellValue)
                        this.rowFiltersManager.updateItem({ item: rowFilter })
                    } else {
                        return false
                    }
                } else {
                    this.rowFiltersManager.insertItem({ item: { key: columnKey, columnKey, cellValues: [cellValue] } })
                }
                this.updateFilterRows()
                return true
            }

            removeFilter = ({ columnKey }) => {
                this.rowFiltersManager.removeItem({ itemKey: columnKey })
                this.updateFilterRows()
            }

            removeFilterValue = ({ columnKey, cellValue }) => {
                var rowFilter = this.rowFiltersManager.getItem({ itemKey: columnKey })
                if (rowFilter && rowFilter.cellValues && rowFilter.cellValues.includes(cellValue)) {
                    rowFilter.cellValues = Utils.Array.removeItem(rowFilter.cellValues, cellValue)
                    if (rowFilter.cellValues.length > 0) {
                        this.rowFiltersManager.updateItem({ item: rowFilter })
                    } else {
                        this.rowFiltersManager.removeItem({ itemKey: columnKey })
                    }
                    this.updateFilterRows()
                    return true
                }
                return false
            }

            removeAllFilters = () => {
                this.rowFiltersManager.removeAllItems()
                this.updateFilterRows()
            }

            removeAllSortOptions = () => {
                this.sortOptionsManager.removeAllItems()
                this.updateSortedRows()
            }

            sortRows({ sortOptions, sortedRowKeys }) {
                if (sortOptions) {
                    sortOptions = sortOptions.map(({ columnKey, orderBy }) => ({ valueKey: columnKey, orderBy }))
                }
                this.datasManager.sortItems({ sortOptions, sortedItemKeys: sortedRowKeys })
                this.bodyRowsManager.sortItems({ sortedItemKeys: this.datasManager.getItemKeys() })
                this.updateTable({ measureColumnWidth: false, columnKey: '行号' })
            }

            addSortOption({ columnKey, orderBy }) {
                var sortOption = this.sortOptionsManager.getItem({ itemKey: columnKey })
                if (sortOption) {
                    if (sortOption.orderBy === orderBy) {
                        return
                    }
                    sortOption.orderBy = orderBy
                    this.sortOptionsManager.updateItem({ item: sortOption })
                } else {
                    this.sortOptionsManager.insertItem({ item: { key: columnKey, columnKey, orderBy } })
                }
                this.updateSortedRows()
            }
            removeSortOption({ columnKey }) {
                this.sortOptionsManager.removeItem({ itemKey: columnKey })
                this.updateSortedRows()
            }
            updateSortedRows() {
                var sortOptions = this.sortOptionsManager.getItems()
                if (sortOptions.length > 0) {
                    sortOptions = sortOptions.map(({ columnKey, orderBy }) => ({ valueKey: columnKey, orderBy }))
                    this.datasManager.sortItems({ sortOptions })
                    this.bodyRowsManager.sortItems({ sortedItemKeys: this.datasManager.getItemKeys() })
                }
                this.updateTable({ measureColumnWidth: false, rowKey: '表头', columnKey: '行号' })
            }

            deleteColumn = ({ columnKey }) => {
                this.headRowCellsManager.removeItem({ itemKey: columnKey })
            }

            insertColumnBefore({ columnKey, beforeColumnKey }) {
                this.headRowCellsManager.insertItem({ item: this._createHeadCellElement({ rowKey: this.headRowCellsManager.key, columnKey }), beforeItemKey: beforeColumnKey })
                this.reload()
            }

            moveColumn({ columnKey, direction }) {
                this.headRowCellsManager.moveItem({ itemKey: columnKey, direction })
                this.updateTable({ measureColumnWidth: false, columnKey })
            }

            moveRow({ rowKey, direction }) {
                this.bodyRowsManager.moveItem({ itemKey: rowKey, direction })
                this.updateTable({ measureColumnWidth: false, columnKey: '行号' })
            }

            updateTable(config) {
                var { measureColumnWidth, rowKey: updateRowKey, columnKey: updateColumnKey, force: forceUpdate } = config || {}
                this.headRowCellsManager.getItems().forEach((cellElement) => {
                    const rowKey = this.headRowCellsManager.key;
                    const columnKey = this.headRowCellsManager.getItemKey(cellElement);
                    if ((updateRowKey || updateColumnKey) && (updateRowKey !== rowKey && updateColumnKey !== columnKey)) {
                        return
                    }
                    var cellValue
                    if (columnKey === '行号') {
                        cellValue = columnKey
                    } else {
                        var localValue = this.localValuesManager.getItemValue({ itemKey: `${rowKey}&${columnKey}`, valueKey: 'value' })
                        if (localValue === undefined) {
                            cellValue = columnKey
                        } else {
                            cellValue = localValue
                        }
                    }
                    cellElement.cellValue = cellValue
                    if (!this.onUpdateHeadCell({ cellElement, cellValue, rowKey, columnKey })) {
                        if (columnKey === '行号') {
                            var innerHTML = `<span>${cellValue}</span>`
                            if (innerHTML !== cellElement.innerHTML) {
                                cellElement.innerHTML = innerHTML
                            }
                        } else {
                            var btnDown = UI.createButton('', event => {
                                var sortOption = this.sortOptionsManager.getItem({ itemKey: columnKey })
                                if (sortOption && sortOption.orderBy === 'desc') {
                                    this.removeSortOption({ columnKey })
                                } else {
                                    this.addSortOption({ columnKey, orderBy: 'desc' })
                                }
                            }, btn => {
                                var sortOption = this.sortOptionsManager.getItem({ itemKey: columnKey })
                                if (sortOption && sortOption.orderBy === 'desc') {
                                    btn.className = 'md-icon-button md-icon-down-selected'
                                } else {
                                    btn.className = 'md-icon-button md-icon-down'
                                }
                            })
                            var btnUp = UI.createButton('', event => {
                                var sortOption = this.sortOptionsManager.getItem({ itemKey: columnKey })
                                if (sortOption && sortOption.orderBy === 'esc') {
                                    this.removeSortOption({ columnKey })
                                } else {
                                    this.addSortOption({ columnKey, orderBy: 'esc' })
                                }
                            }, btn => {
                                var sortOption = this.sortOptionsManager.getItem({ itemKey: columnKey })
                                if (sortOption && sortOption.orderBy === 'esc') {
                                    btn.className = 'md-icon-button md-icon-up-selected'
                                } else {
                                    btn.className = 'md-icon-button md-icon-up'
                                }
                            })
                            var btnFilter = document.createElement('button')
                            if (this.rowFiltersManager.getItem({ itemKey: columnKey })) {
                                btnFilter.className = 'md-icon-button md-icon-filter-selected'
                            } else {
                                btnFilter.className = 'md-icon-button md-icon-filter'
                            }
                            UI.addEventListeners(btnFilter, {
                                onClick: () => {
                                    var totalCount = 0
                                    const cellValueCountMap = new Map()
                                    this.bodyRowsManager.getItems().forEach(rowElement => {
                                        totalCount += 1
                                        const cellValue = this.bodyRowCellsManagers.getItem({ itemKey: this.bodyRowsManager.getItemKey(rowElement) }).getItem({ itemKey: columnKey }).cellValue
                                        if (!cellValueCountMap.has(cellValue)) {
                                            cellValueCountMap.set(cellValue, 0)
                                        }
                                        cellValueCountMap.set(cellValue, cellValueCountMap.get(cellValue) + 1)
                                    })
                                    const buttons = []
                                    Array.from(cellValueCountMap.keys()).forEach((value) => {
                                        var count = cellValueCountMap.get(value)
                                        buttons.push({
                                            name: `计数 ${count}\t丨\t${value}\t`,
                                            onClick: btn => {
                                                var rowFilter = this.rowFiltersManager.getItem({ itemKey: columnKey })
                                                if (rowFilter && rowFilter.cellValues && rowFilter.cellValues.includes(value)) {
                                                    if (this.removeFilterValue({ columnKey, cellValue: value })) {
                                                        btn.style.color = null
                                                    }
                                                } else {
                                                    this.addFilterValue({ columnKey, cellValue: value })
                                                    btn.style.color = '#006659'
                                                }
                                                if (this.rowFiltersManager.getItem({ itemKey: columnKey })) {
                                                    btnFilter.className = 'md-icon-button md-icon-filter-selected'
                                                } else {
                                                    btnFilter.className = 'md-icon-button md-icon-filter'
                                                }
                                                return false
                                            },
                                            onCreate: btn => {
                                                var rowFilter = this.rowFiltersManager.getItem({ itemKey: columnKey })
                                                if (rowFilter && rowFilter.cellValues && rowFilter.cellValues.includes(value)) {
                                                    btn.style.color = '#006659'
                                                } else {
                                                    btn.style.color = null
                                                }
                                            }
                                        })
                                    })
                                    UI.createHoverMenu({
                                        element: cellElement,
                                        buttons,
                                        textAlign: 'left',
                                        isShowNow: true,
                                        scrollable: true,
                                        title: `计数 ${totalCount}\t丨\t${this.localValuesManager.getItemValue({ itemKey: `表头&${columnKey}`, valueKey: 'value' }) || columnKey}`
                                    })
                                },
                                onRightClick: () => {
                                    MD.UI.createHoverMenu({
                                        element: btnFilter,
                                        isShowNow: true,
                                        buttons: Array.of(
                                            ['取消筛选', () => {
                                                this.removeFilter({ columnKey })
                                                btnFilter.className = 'md-icon-button md-icon-filter'
                                            }, (btn) => {
                                                btn.hidden = this.rowFiltersManager.getItem({ itemKey: columnKey }) ? false : true
                                            }],
                                            ['全选', () => {
                                                var cellValues = []
                                                this.bodyRowsManager.getItems().forEach(rowElement => {
                                                    const cellValue = this.bodyRowCellsManagers.getItem({ itemKey: this.bodyRowsManager.getItemKey(rowElement) }).getItem({ itemKey: columnKey }).cellValue
                                                    if (!cellValues.includes(cellValue)) {
                                                        cellValues.push(cellValue)
                                                    }
                                                })
                                                this.addFilter({ columnKey, cellValues })
                                                btnFilter.className = 'md-icon-button md-icon-filter-selected'
                                            }],
                                            ['反选', () => {
                                                var cellValues = []
                                                this.bodyRowsManager.getItems().forEach(rowElement => {
                                                    const cellValue = this.bodyRowCellsManagers.getItem({ itemKey: this.bodyRowsManager.getItemKey(rowElement) }).getItem({ itemKey: columnKey }).cellValue
                                                    if (!cellValues.includes(cellValue)) {
                                                        cellValues.push(cellValue)
                                                    }
                                                })
                                                var rowFilter = this.rowFiltersManager.getItem({ itemKey: columnKey })
                                                if (rowFilter) {
                                                    cellValues = cellValues.filter(cellValue => !rowFilter.cellValues.includes(cellValue))
                                                }
                                                if (cellValues.length > 0) {
                                                    this.addFilter({ columnKey, cellValues })
                                                    btnFilter.className = 'md-icon-button md-icon-filter-selected'
                                                } else {
                                                    this.removeFilter({ columnKey })
                                                    btnFilter.className = 'md-icon-button md-icon-filter'
                                                }
                                            }, (btn) => {
                                                btn.hidden = this.rowFiltersManager.getItem({ itemKey: columnKey }) ? false : true
                                            }]
                                        ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                                    });
                                }
                            })
                            var innerHTML = `<div style="display:flex;align-items:center"><span style="flex:1;margin:0 5px">${cellValue}</span><div style="display:flex;flex-direction:column;margin:2.5px -5px 2.5px 0;"></div></div>`
                            if (innerHTML !== cellElement.innerHTML) {
                                cellElement.innerHTML = innerHTML
                            }

                            Array.of(btnFilter, btnUp, btnDown).forEach(btn => {
                                cellElement.lastElementChild.lastElementChild.appendChild(btn)
                            })
                        }
                    }
                })
                this.bodyRowCellsManagers.getItems().forEach((manager, rowIndex) => {
                    manager.getItems().forEach(cellElement => {
                        const rowKey = manager.key;
                        const columnKey = manager.getItemKey(cellElement);
                        if ((updateRowKey || updateColumnKey) && (updateRowKey !== rowKey && updateColumnKey !== columnKey)) {
                            return
                        }
                        var cellValue
                        if (columnKey === '行号') {
                            cellValue = rowIndex + 1
                        } else {
                            var localValue = this.localValuesManager.getItemValue({ itemKey: `${rowKey}&${columnKey}`, valueKey: 'value' })
                            if (localValue === undefined) {
                                cellValue = this.datasManager.getItemValue({ itemKey: rowKey, valueKey: columnKey })
                                cellValue = cellValue === undefined ? '' : cellValue
                                cellElement.style.fontWeight = null
                            } else {
                                cellValue = localValue
                                cellElement.style.fontWeight = '900'
                            }
                        }
                        if (forceUpdate || cellValue !== cellElement.cellValue) {
                            cellElement.cellValue = cellValue
                            if (!this.onUpdateBodyCell({ cellElement, cellValue, rowKey, columnKey })) {
                                cellElement.innerHTML = `<span>${cellValue}</span>`
                            }
                        }
                    })
                })
                if (measureColumnWidth) {
                    var visibleColumnKeys = this.headRowCellsManager.getVisibleItems().map(item => item.key)
                    var colWidths = {};
                    visibleColumnKeys.forEach(itemKey => {
                        var maxWidth = 0;
                        maxWidth = Math.max(maxWidth, this.headRowCellsManager.getItem({ itemKey }).getBoundingClientRect().width);
                        this.bodyRowCellsManagers.getItems().forEach(manager => {
                            maxWidth = Math.max(maxWidth, manager.getItem({ itemKey }).getBoundingClientRect().width);
                        })
                        colWidths[itemKey] = `${maxWidth}px`
                    })
                    visibleColumnKeys.forEach(itemKey => {
                        var cellElement = this.headRowCellsManager.getItem({ itemKey })
                        cellElement.style.minWidth = cellElement.style.maxWidth = colWidths[itemKey];
                        this.bodyRowCellsManagers.getItems().forEach(manager => {
                            cellElement = manager.getItem({ itemKey })
                            cellElement.style.minWidth = cellElement.style.maxWidth = colWidths[itemKey];
                        })
                    })
                }
            }

            show() {
                UI.show(this.tableElement)
            }

            hide() {
                UI.hide(this.tableElement)
            }
        }

        const Manager = {
            ListManager, ObjectDataListManager, ElementListManager, TableManager
        }

        const Utils = {
            Array: {
                removeItem(array, item) {
                    array.splice(array.indexOf(item), 1)
                    return array
                },
                replaceItem(array, index, item) {
                    array.splice(index, 1, item)
                    return array
                },
                moveItem(array, fromIndex, direction, condition) {
                    var toIndex
                    if (direction > 0 && (fromIndex + direction) > array.length - 1) {
                        direction = array.length - 1 - fromIndex
                    } else if (direction < 0 && (fromIndex + direction) < 0) {
                        direction = 0 - fromIndex
                    }
                    if (condition) {
                        toIndex = this.findItemIndex(array, fromIndex, direction, condition)
                    } else {
                        toIndex = fromIndex + direction
                    }
                    if (fromIndex === -1 || toIndex < 0 || toIndex > array.length) {
                        return array
                    }

                    const item = array.splice(fromIndex, 1)[0];
                    if (fromIndex < toIndex) {
                        array.splice(toIndex, 0, item);
                    } else {
                        array.splice(toIndex, 0, item);
                    }

                    return array
                },
                insertItem(array, newItem, beforeItem) {
                    if (beforeItem) {
                        array.splice(array.indexOf(beforeItem), 0, newItem)
                    } else {
                        array.push(newItem)
                    }
                    return array
                },
                findItemIndex(array, fromIndex, direction, condition) {
                    if (direction > 0) {
                        for (let i = fromIndex + 1; i < array.length; i++) {
                            if (condition(array[i])) {
                                if (--direction === 0) {
                                    return i;
                                }
                            }
                        }
                    } else {
                        for (let i = fromIndex - 1; i >= 0; i--) {
                            if (condition(array[i])) {
                                if (++direction === 0) {
                                    return i;
                                }
                            }
                        }
                    }
                    return fromIndex;
                }
            },
            Clipboard: {
                copyText(text) {
                    return navigator.clipboard.writeText(text)
                },
                copyImage({ element }) {
                    return new Promise((resolve, reject) => {
                        html2canvas(element, { backgroundColor: 'transparent', scale: 4 }).then(canvas => {
                            canvas.toBlob(blob => {
                                navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]).then(function () {
                                    resolve()
                                }).catch(reject)
                            }, 'image/png');
                        }).catch(reject);
                    })
                },
                async getText() {
                    try {
                        return await navigator.clipboard.readText();
                    } catch (err) {
                        return '';
                    }
                }
            }
        }

        const UI = {
            isHidden(element) {
                return !element || element.hidden || element.style.display === 'none'
            },
            show(element) {
                if (!element) {
                    return
                }
                if (element._originalDisplay === undefined) {
                    element._originalDisplay = element.style.display
                }
                element.style.display = element._originalDisplay
                element.hidden = false
            },
            hide(element) {
                if (!element) {
                    return
                }
                if (element._originalDisplay === undefined) {
                    element._originalDisplay = element.style.display
                }
                element.style.display = 'none'
                element.hidden = true
            },

            addEventListeners(element, { onClick, onDblClick, onLongPress, onRightClick, onRightDblClick, onRightLongPress }) {

                if (onRightClick || onRightDblClick || onRightLongPress) {
                    element.addEventListener('contextmenu', event => {
                        event.preventDefault()
                        event.stopPropagation()
                    })
                }

                const dblClickDuration = 380
                const longPressDuration = 600

                let clickTimes = 0;
                let clickTime = 0;
                let clickTimer

                let rightClickTimes = 0;
                let rightClickTime = 0;
                let rightClickTimer

                const onDown = event => {
                    event.preventDefault();
                    event.stopPropagation();
                    if (event.button === 2) {
                        rightClickTimes += 0.5;
                        if (rightClickTimes === 0.5) {
                            rightClickTime = new Date().getTime();
                            rightClickTimer = setTimeout(() => {
                                if (rightClickTimes === 1) {
                                    rightClickTimes = 0;
                                    onRightClick && onRightClick(element);
                                }
                            }, dblClickDuration);
                        }
                    } else {
                        clickTimes += 0.5;
                        if (clickTimes === 0.5) {
                            clickTime = new Date().getTime();
                            clickTimer = setTimeout(() => {
                                if (clickTimes === 1) {
                                    clickTimes = 0;
                                    onClick && onClick(element);
                                }
                            }, dblClickDuration);
                        }
                    }
                };

                const onUp = event => {
                    event.preventDefault();
                    event.stopPropagation();
                    if (event.button === 2) {
                        rightClickTimes += 0.5;

                        const timeDiff = new Date().getTime() - rightClickTime;
                        if (timeDiff <= dblClickDuration) {
                            if (rightClickTimes === 2) {
                                clearTimeout(rightClickTimer);
                                rightClickTimes = 0;
                                onRightDblClick && onRightDblClick(element);
                            }
                        } else if (timeDiff > dblClickDuration && timeDiff < longPressDuration) {
                            rightClickTimes = 0;
                            onRightClick && onRightClick(element);
                        } else if (timeDiff >= longPressDuration) {
                            rightClickTimes = 0;
                            onRightLongPress && onRightLongPress(element);
                        }
                    } else {
                        clickTimes += 0.5;

                        const timeDiff = new Date().getTime() - clickTime;
                        if (timeDiff <= dblClickDuration) {
                            if (clickTimes === 2) {
                                clearTimeout(clickTimer);
                                clickTimes = 0;
                                onDblClick && onDblClick(element);
                            }
                        } else if (timeDiff > dblClickDuration && timeDiff < longPressDuration) {
                            clickTimes = 0;
                            onClick && onClick(element);
                        } else if (timeDiff >= longPressDuration) {
                            clickTimes = 0;
                            onLongPress && onLongPress(element);
                        }
                    }
                };

                element.addEventListener('mousedown', onDown);
                element.addEventListener('mouseup', onUp);
            },

            addStyle(styleText) {
                var styleElement = document.createElement('style');
                styleElement.setAttribute('type', 'text/css')
                styleElement.innerHTML = styleText;
                document.head.appendChild(styleElement);
            },
            showToast(message, duration = 1500) {
                const toastElement = document.createElement('div')
                toastElement.innerHTML = message
                toastElement.className = 'md-toast'
                document.body.appendChild(toastElement)
                setTimeout(() => toastElement.remove(), duration);
            },
            copyText(text) {
                Utils.Clipboard.copyText(text).then(res => this.showToast('已复制：\n\n' + text)).catch(err => this.showToast('复制失败：\n' + err))
            },
            copyImage({ element }) {
                Utils.Clipboard.copyImage({ element }).then(res => this.showToast('已复制')).catch(err => this.showToast('复制失败：\n' + err))
            },
            downloadImage({ element, filename }) {
                html2canvas(element, { backgroundColor: 'transparent', scale: 4 }).then(canvas => {
                    canvas.toBlob(blob => {
                        const a = document.createElement('a');
                        a.download = filename;
                        a.href = URL.createObjectURL(blob);
                        a.click();
                        this.showToast('开始下载')
                    }, 'image/png');
                }).catch(err => {
                    this.showToast('下载失败：\n' + err)
                });
            },
            createButton(name, onClick, onCreate) {
                const button = document.createElement('button');
                button.innerText = name;
                onClick && button.addEventListener('click', (event) => {
                    event.stopPropagation()
                    event.preventDefault()
                    onClick(event)
                });
                onClick && button.addEventListener('mousedown', (event) => {
                    event.stopPropagation()
                    event.preventDefault()
                });
                onCreate && onCreate(button)
                return button;
            },
            createHoverMenu({ element, onMouseEnter, onMouseLeave, scrollable, buttons, isShowNow, textAlign, title }) {
                var menuElement
                element._originalZIndex = element.style.zIndex
                const mouseenter = () => {
                    if (element.querySelector('.md-hoverbtns')) { return }


                    menuElement = document.createElement('div');
                    menuElement.className = 'md-hoverbtns';
                    if (scrollable) {
                        menuElement.style.maxHeight = '300px'
                        menuElement.style.overflowX = 'hidden'
                        menuElement.style.overflowY = 'scroll'
                    }
                    element.style.zIndex = 2

                    menuElement.addEventListener('mousedown', event => event.stopPropagation())

                    const overlayerDiv = document.createElement('div');
                    overlayerDiv.className = 'overlayer';
                    overlayerDiv.style.height = '100%'
                    overlayerDiv.style.width = '100%'
                    overlayerDiv.style.left = '-30px'
                    overlayerDiv.style.top = '-30px'
                    overlayerDiv.style.padding = '30px'
                    overlayerDiv.style.background = 'transparent'
                    overlayerDiv.addEventListener('click', () => {
                        menuElement.remove();
                    });
                    menuElement.appendChild(overlayerDiv)

                    const data = onMouseEnter && onMouseEnter(element)
                    if (title) {
                        buttons = [{ name: title }].concat(buttons)
                    }
                    buttons.forEach(({ name, onClick, onCreate }, index) => {
                        const btn = this.createButton(name, e => {
                            if (onClick) {
                                if (onClick(btn, data) === false) {
                                    return
                                }
                            }
                            menuElement.remove()
                        }, btn => {
                            if (textAlign === 'left') {
                                btn.style.textAlign = 'left'
                                btn.style.whiteSpace = 'pre'
                            }
                            if (index === 0 && title) {
                                btn.style.position = 'sticky'
                                btn.style.top = 0
                                btn.style.fontWeight = 'bold'
                                btn.style.borderBottom = '1px solid #eee'
                            }
                            onCreate && onCreate(btn, data)
                        }
                        )
                        menuElement.appendChild(btn);
                    });
                    element.appendChild(menuElement);
                }
                const mouseleave = () => {
                    element.style.zIndex = element._originalZIndex
                    element.removeEventListener('mouseenter', mouseenter)
                    element.removeEventListener('mouseleave', mouseleave)
                    menuElement && menuElement.remove();
                    onMouseLeave && onMouseLeave()
                }
                if (isShowNow) {
                    mouseenter()
                } else {
                    element.addEventListener('mouseenter', mouseenter);
                }
                element.addEventListener('mouseleave', mouseleave);
                return menuElement
            },
            createHoverEditor({ element, value, onSave }) {

                const editorElement = document.createElement('div');
                editorElement.addEventListener('mousedown', event => { event.stopPropagation() })
                editorElement.id = 'edit';
                editorElement.className = 'md-hoverbtns';
                editorElement.style.top = '0';
                editorElement.style.left = '0';
                editorElement.style.width = element.clientWidth + 'px';

                const overlayerDiv = document.createElement('div');
                overlayerDiv.className = 'overlayer';
                overlayerDiv.addEventListener('click', () => {
                    editorElement.remove();
                });

                const inputElement = document.createElement('input');
                inputElement.style.width = element.clientWidth + 'px';
                inputElement.style.height = element.clientHeight + 'px';
                inputElement.value = value;
                inputElement.addEventListener('click', e => e.stopPropagation());

                const saveBtn = document.createElement('button');
                saveBtn.textContent = '保存';
                saveBtn.addEventListener('click', () => {
                    if (inputElement.value === value) {
                        editorElement.remove()
                    } else {
                        onSave(inputElement.value, value) && editorElement.remove()
                    }
                });

                editorElement.appendChild(overlayerDiv);
                editorElement.appendChild(inputElement);
                editorElement.appendChild(saveBtn);
                element.appendChild(editorElement);

                inputElement.focus();

                return editorElement
            },
            createCheckboxPopup({ onCreatePopup, options, onMoveOption, onGetIndex, withoutCheckbox }) {
                const popup = document.createElement('div');
                popup.id = 'md-popup';
                popup.style.width = '200px'
                popup.style.maxHeight = '700px'
                popup.style.overflowY = 'scroll'
                onCreatePopup && onCreatePopup(popup)

                popup.insertOption = popup.createOption = ({ key, lableText, onCreateOption, checked, onCheckChange }, index) => {
                    const label = document.createElement('label');
                    label.key = label.htmlFor = key

                    const checkbox = document.createElement('input');
                    checkbox.type = 'checkbox';
                    checkbox.value = checkbox.id = key;
                    checkbox.checked = checked;
                    checkbox.addEventListener('change', () => {
                        onCheckChange && onCheckChange(checkbox.checked)
                    })

                    const upButton = document.createElement('button');
                    upButton.innerHTML = ' ↑ ';
                    upButton.addEventListener('click', () => moveOption(key, -1));

                    const downButton = document.createElement('button');
                    downButton.innerHTML = ' ↓ ';
                    downButton.addEventListener('click', () => moveOption(key, 1));

                    label.appendChild(upButton);
                    label.appendChild(downButton);
                    !withoutCheckbox && label.appendChild(checkbox);
                    if (onCreateOption) {
                        label.appendChild(onCreateOption(index, label, popup))
                    } else {
                        const span = document.createElement('span');
                        span.innerHTML = lableText || checkbox.value
                        label.appendChild(span)
                    }
                    popup.insertBefore(label, popup.children[index + 1]);
                }

                options.forEach(popup.createOption);

                function moveOption(key, direction) {
                    const index = onGetIndex(key)
                    const directionIndex = index + direction
                    if (index !== -1 && directionIndex >= 0 && directionIndex < options.length) {
                        if (direction > 0) {
                            popup.insertBefore(popup.children[directionIndex], popup.children[index])
                        } else {
                            popup.insertBefore(popup.children[index], popup.children[directionIndex])
                        }
                        onMoveOption(key, direction)
                    }
                }

                const overlay = document.createElement('div');
                overlay.id = 'md-overlay';
                overlay.addEventListener('click', function () {
                    document.body.removeChild(popup);
                    document.body.removeChild(overlay);
                });

                document.body.appendChild(overlay);
                document.body.appendChild(popup);

                return popup
            },
            createPopup(onCreate) {
                const popup = document.createElement('div');
                popup.id = 'md-popup';
                onCreate(popup)

                const overlay = document.createElement('div');
                overlay.id = 'md-overlay';
                overlay.addEventListener('click', function () {
                    popup.remove()
                });

                popup.remove = () => {
                    document.body.removeChild(popup);
                    document.body.removeChild(overlay);
                }

                document.body.appendChild(overlay);
                document.body.appendChild(popup);
            },
            waitForElement({ selector, delay = 0, timeout = 5000 }) {
                return new Promise((resolve, reject) => {
                    let startTime
                    setTimeout(() => {
                        const targetNode = document.querySelector(selector);
                        if (targetNode) {
                            resolve(targetNode);
                        } else {
                            const observer = new MutationObserver((mutations) => {
                                mutations.forEach((mutation) => {
                                    const targetNode = document.querySelector(selector);
                                    if (targetNode) {
                                        observer.disconnect();
                                        resolve(targetNode);
                                    }
                                });
                            });
                            observer.observe(document.documentElement, { childList: true, subtree: true });
                            if (timeout && !startTime) {
                                startTime = new Date().getTime()
                                const interval = setInterval(() => {
                                    if (new Date().getTime() - startTime > timeout) {
                                        reject(new Error(`It's time out for waiting for '${selector}'.`))
                                        observer.disconnect()
                                        clearInterval(interval)
                                    }
                                }, 100)
                            }
                        }
                    }, delay);
                });
            }
        }

        const xhrListenersMap = new function () {
            const map = new Map()
            this.getListener = (url, listenerName) => {
                var key = Array.from(map.keys()).find(key => url.startsWith(key))
                return key ? map.get(key)[listenerName] : undefined
            }
            this.setListener = (url, listenerName, listener) => {
                var listeners = map.get(url)
                if (!listeners) {
                    listeners = {}
                }
                listeners[listenerName] = listener
                map.set(url, listeners)
            }
            this.urls = () => map.keys()

            var realXMLHttpRequest = window.XMLHttpRequest;
            window.XMLHttpRequest = function () {
                var proxyRequest = new realXMLHttpRequest();
                Array.of('onload', 'send').forEach(functionName => {
                    var originalFunction = proxyRequest[functionName]
                    var proxyFunction = function (...args) {
                        var listener = xhrListenersMap.getListener(proxyRequest._url, functionName)
                        try { listener && listener(proxyRequest, ...args) } catch (e) { console.error(e) }
                        if (originalFunction) {
                            return originalFunction.apply(this, args)
                        }
                    };
                    if (!originalFunction) {
                        proxyRequest[functionName] = proxyFunction
                    }
                    Object.defineProperty(proxyRequest, functionName, {
                        get: function () { return proxyFunction },
                        set: function (val) { originalFunction = val }
                    });
                })
                return proxyRequest;
            };
        }

        class ProxyRequest {
            static initRequester(proxyRequester = { post, get }) {
                ProxyRequest.proxyRequester = proxyRequester
            }
            constructor(url) {
                this.url = url
                xhrListenersMap.setListener(this.url, 'send', (request, ...args) => {
                    const cloneRequest = request
                    cloneRequest.sendArgs = args || []
                    this.request = cloneRequest
                })
            }
            send(params) {
                if (this.request) {
                    var url = this.request._url
                    params = params || JSON.parse(this.request.sendArgs[0])
                    if (this.request._method === 'POST') {
                        ProxyRequest.proxyRequester.post(url, params)
                    } else if (this.request._method === 'GET') {
                        ProxyRequest.proxyRequester.get(url)
                    }
                }
            }
            onload(listener) {
                xhrListenersMap.setListener(this.url, 'onload', listener)
            }
        }
        const XHR = {
            ProxyRequest,
            downloadZipFiles(zipFilename, files) {
                UI.showToast(`正在生成文件：${zipFilename}`)
                const tasks = files.map(({ filename, url }) => {
                    return new Promise((resolve, reject) => {
                        const xhr = new XMLHttpRequest();
                        xhr.open("GET", url, true);
                        xhr.responseType = "blob";
                        xhr.onload = () => xhr.status === 200 ? resolve({ filename, blob: xhr.response }) : reject(new Error("Failed to download image: " + url))
                        xhr.onerror = () => reject(new Error("Failed to download image: " + url));
                        xhr.send();
                    });
                })
                Promise.all(tasks)
                    .then(blobs => {
                        const zip = new JSZip();
                        blobs.forEach(({ filename, blob }) => {
                            zip.file(filename, blob, { binary: true })
                        })
                        return zip.generateAsync({ type: "blob" });
                    })
                    .then(zipBlob => {
                        UI.showToast(`准备下载文件：${zipFilename}`);
                        saveAs(zipBlob, zipFilename)
                    })
                    .catch(error => console.error("Failed to create ZIP file:", error));
            }
        }

        UI.addStyle(`
                .md-hoverbtns {
                    position: absolute;
                    top: 40%;
                    left: 40%;
                    background: white;
                    z-index: 4;
                    font-weight: normal;
                    box-shadow: 0 0 5px 5px #eee;
                    display: flex;
                    flex-direction:column;
                    color:rgba(0,0,0,.85);
                }
                .md-hoverbtns>.overlayer{
                    width: 4000px;
                    height: 4000px;
                    position: absolute;
                    top: -1000px;
                    left: -1000px;
                    z-index: -1;
                    background: rgb(0,0,0,0.2);
                }
                .md-hoverbtns>button:hover{
                    background: #eee;
                    cursor: pointer;
                }
                .md-hoverbtns>button{
                    padding: 10px;
                    min-width: 72px;
                    background: white;
                }
                .md-hoverbtns>input{
                    height: 33px;
                    min-width: 72px;
                    background: white;
                }

                .md-toast{
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translateX(-50%);
                    padding: 10px 20px;
                    background: rgba(0,0,0,0.8);
                    color: #fff;
                    border-radius: 4px;
                    z-index: 9999;
                    white-space: pre-line;
                    text-align: left;
                    width: max-content;
                }

                #md-popup {
                    position: fixed;
                    width: 400px;
                    display:flex;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    background-color: #fff;
                    padding: 20px;
                    z-index: 9999;
                    align-items:flex-start;
                    flex-wrap: wrap;
                }
                #md-popup > label{
                    display:flex;
                    flex-direction:row;
                    align-items:center;
                    font-size: 14px;
                }
                #md-popup > label > .content{
                    display: flex;
                    flex-direction:row;
                    align-items: center;
                }
                #md-popup > label > input[type=checkbox]{
                    height: 20px;
                    margin: 10px;
                }
                #md-popup > label > .content > input[type=text]{
                    border: 1px solid #eee;
                    width: 180px;
                    height: 33px;
                }
                #md-popup > label > *>* >select, #md-popup > label > * >select{
                    color: unset;
                    border-color: #ddd;
                    margin-right: 10px;
                    height: 33px;
                }
                #md-popup > label > .content > textarea{
                    width: 300px;
                    height: 65px;
                    margin: 10px;
                    border: 1px solid #eee;
                    padding: 10px;
                }
                #md-popup > button:hover,#md-popup > label > button:hover,#md-popup > label > *> *>button:hover{
                    background:#eee;
                    cursor: pointer;
                }
                #md-popup > label > span{
                    line-height:30px;
                }
                #md-overlay {
                    position: fixed;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background-color: rgba(0, 0, 0, 0.5);
                    z-index: 9998;
                }
            `)

        return { Storage, Manager, UI, Utils, XHR };
    })();

    class SailorInfoTable extends MD.Manager.TableManager {
        constructor({ key = 'sailorInfo', tableElement, reload, cardsElement, sailorInfos, activityInfo, volunteerInfo, uneditableColumns = ['星球编号', '星球昵称', '星球头像', '打卡提醒', '已打卡天数', '距上岸天数', '可打卡天数', '可休息天数', '是否已打卡', '航海期数', '话术复制'].concat(new Array(21).fill('').map((v, i) => `Day${i + 1}`))
        }) {
            super({
                key, tableElement,
                datas: sailorInfos,
                reload,
                onInit: (self) => {
                    self.activityInfo = activityInfo
                    self.volunteerInfo = volunteerInfo

                    Array.from(cardsElement.children).forEach(cardElement => {
                        cardElement.key = parseInt(cardElement.querySelector('div.header > div.left.v-row > div > div.groupNumber > span').textContent.substring(5))
                    })
                    self.cardsManager = new MD.Manager.ElementListManager({ key: `${self.key}.cardsManager`, parentElement: cardsElement, cachingEnabled: { sortedItemKeys: true, hiddenItemKeys: true } })

                    self.copyBtnConfigsManager = new MD.Manager.ObjectDataListManager({
                        key: `${self.key}.copyBtnConfigs`,
                        items: Array.of(
                            ['船员微信备注', `{微信昵称}（{星球昵称}）`
                            ],
                            ['1.1.3添加好友', `我是生财有术{航海名称}航海的志愿者，你的报名审核通过了，我来邀你进群～`
                            ],
                            ['2.1介绍自己', `{微信昵称} 你好呀，我是{航海名称}志愿者 {志愿者微信昵称}，来邀请你上船～

                                                看到你之前也参加过航海，算是老航海人啦，具体的航海规则咱们就不说了，哈哈哈。

                                                这次由我来为你保驾护航啦，一起成功上岸哇，来，上船！`
                            ],
                            ['2.2开船时间', '开船时间是3月20号（周三）晚上8点，视频号直播形式先做航海迭代介绍，接着教练群内分享【开船第一课】，让你快速了解整个项目，记得预留时间哈。'
                            ],
                            ['2.3上船流程', `接下来我会邀请你进入2个航海群，一个是航海交流群（日常交流答疑），一个是禁言群（重要信息同步）

                                                在此之前需要你关注一下#公众号：生财有术服务号

                                                后续会提醒打卡、好事推荐，都会通过服务号传达给你，通知要打开才可以收到消息哈。`
                            ],
                            ['3.1介绍群', `接下来我会邀请你进入两个群哈，邀请之前我先来简单介绍一下~

                                            【航海交流群】是航海的主要阵地，是大家交流、答疑、分享、链接的地方
                                            【禁言通知群】这个群是咱们这个项目的通知群，主要用于重要信息同步，关注禁言群，重要信息不错过`
                            ],
                            ['3.2邀请进群', `点击下面的链接就可以进群啦。

                                                为了大家交流、链接更方便，需要你在【航海交流群】按照格式：昵称-地区-总参与航海期数，更新你的群昵称。

                                                你的群昵称是：{群昵称}

                                                这是后台根据你往期参与的航海数据，生成的群昵称，你也可以自己再修改【昵称】和【地区】

                                                改完后可以拍拍我哈`
                            ],
                            ['游戏任务版打卡提醒：昨天打卡前提醒的', ''],
                            ['游戏任务版打卡提醒：昨天打卡后提醒的', ''],
                        ).map(a => ({ key: a[0], btnName: a[0], content: a[1].split('\n').map(t => t.trim()).join('\n'), btnType: a[2] || 'text' })),
                        cachingEnabled: { items: true, sortedItemKeys: true, newItemKeys: true }
                    })

                    self.dayColumnPattern = /^Day([1-9]|1\d|2[0-1])$/
                },
                cachingEnabled: { rows: true, columns: true, filters: true, displayMode: true },
                columns: Array.of('星球编号', '星球昵称', '星球头像', '微信昵称', '群昵称', '微信号', '手机号', '上船进度', '备注', '话术复制',
                    '打卡提醒',
                    '已打卡天数', '距上岸天数', '可打卡天数', '可休息天数', '是否已打卡', '航海期数', '所在城市').concat(new Array(21).fill('').map((v, i) => `Day${i + 1}`)).map(key => {
                        return uneditableColumns.includes(key) ? { key, editable: false } : { key }
                    })
            })
            this.bodyRowsManager.addManagerListener({
                sortItems: ({ items, sortedItemKeys }) => {
                    this.cardsManager.sortItems({ sortedItemKeys })
                },
                showItem: ({ itemKey }) => {
                    this.cardsManager.showItem({ itemKey })
                },
                hideItem: ({ itemKey }) => {
                    this.cardsManager.hideItem({ itemKey })
                },
                clearCache: () => {
                    this.cardsManager.clearCache()
                }
            })
            this.bodyRowCellsManagers.getItems().forEach(manager => {
                const markCellElement = manager.getItem({ itemKey: '备注' })
                markCellElement.addEventListener('mouseenter', () => {
                    markCellElement.firstElementChild.style.height = null
                })
                markCellElement.addEventListener('mouseleave', () => {
                    markCellElement.firstElementChild.style.height = '33px'
                })
                new Array(21).fill(1).forEach((item, index) => {
                    var recordElement = manager.getItem({ itemKey: `Day${index + 1}` })
                    recordElement.addEventListener('mouseenter', () => {
                        if (this.recordDisplay === 'detail') {
                            return
                        }
                        var recordHTML = `<div class="break-line" style="max-width: 300px;">${this.datasManager.getItemValue({ itemKey: manager.key, valueKey: `record-Day${index + 1}` })}</div>`
                        if (recordElement.innerHTML !== recordHTML) {
                            recordElement.originalInnerHTML = recordElement.innerHTML
                            recordElement.innerHTML = recordHTML
                        }
                    })
                    recordElement.addEventListener('mouseleave', () => {
                        if (this.recordDisplay === 'detail') {
                            return
                        }
                        if (recordElement.originalInnerHTML) {
                            recordElement.innerHTML = recordElement.originalInnerHTML
                        }
                    })
                })
            })
            this.updateCopyBtns()
            this.copyBtnConfigsManager.addManagerListener({
                sortItems: ({ items, sortedItemKeys }) => {
                    this.cardsManager.getItems().forEach(({ copyBtnsManager }) => {
                        copyBtnsManager.sortItems({ sortedItemKeys })
                    })
                    this.bodyRowCellsManagers.getItems().forEach(manager => {
                        const { copyBtnsManager } = manager.getItem({ itemKey: '话术复制' })
                        copyBtnsManager.sortItems({ sortedItemKeys })
                    })
                },
                removeItem: ({ itemKey }) => {
                    this.cardsManager.getItems().forEach(({ copyBtnsManager }) => {
                        copyBtnsManager.removeItem({ itemKey })
                    })
                    this.bodyRowCellsManagers.getItems().forEach(manager => {
                        const { copyBtnsManager } = manager.getItem({ itemKey: '话术复制' })
                        copyBtnsManager.removeItem({ itemKey })
                    })
                }
            })
            if (!this.activityInfo.isSailing) {
                this.hideColumn({ columnKey: '打卡提醒', force: false })
                this.hideColumn({ columnKey: '可打卡天数', force: false })
                this.hideColumn({ columnKey: '可休息天数', force: false })
                this.hideColumn({ columnKey: '是否已打卡', force: false })
            }
            for (let i = 21; i > this.activityInfo['航海天数']; i--) {
                this.hideColumn({ columnKey: `Day${i}`, force: false })
            }

            this.switchDisplayMode(this._cache.get('displayMode'))
        }

        switchRecordDisplay(display) {
            this.recordDisplay = display
            this.updateTable({ force: true })
        }

        insertColumnBefore({ columnKey, beforeColumnKey }) {
            super.insertColumnBefore({ columnKey, beforeColumnKey })
            this.updateTable()
        }

        clearCache() {
            super.clearCache()
            this.copyBtnConfigsManager.clearCache()
        }

        getIslandGameText({ rowKey, isYesterdayRemindedBeforeRecord }) {

            if (!this.islands) {
                this.islands = Array.of(
                    ['奇迹之岛', '奇迹之岛是一个神奇而美丽的地方，岛上有一座令人惊叹的瀑布，还有迷人的彩色珊瑚礁。每年春天，岛上会出现罕见的飞行鱼群，给人们带来无尽的惊喜。'],
                    ['宝藏湾', '宝藏湾是一个隐藏着无数宝藏的神秘海湾。岛上长满了翠绿的森林，悬崖上有一座古老的灯塔，传说在满月之夜，湾中的水会变成金子，让人们不禁想要探寻其中的秘密。'],
                    ['蓝色天堂', '蓝色天堂是一个宁静而宜人的度假胜地。岛上有绵延数里的白沙滩和清澈碧蓝的海水，是潜水和冲浪的绝佳场所。当夜幕降临时，天空中的繁星和发光的生物会让整个岛屿闪耀如同仙境。'],
                    ['龙之峡谷', '龙之峡谷是一个神秘而壮观的地方，被陡峭的峭壁环绕着。岛上有一座古老的龙神庙，相传每隔百年会有真龙现身。登上峡谷的山顶，可以俯瞰整个岛屿的壮丽景色，仿佛置身于云端。'],
                    ['幻境之林', '幻境之林是一个神奇而迷人的森林岛屿。岛上布满了奇形怪状的树木和绚丽多彩的花朵。在这片神秘的森林中，隐藏着许多魔法生物和宝藏，只有真正勇敢的冒险者才能找到它们。'],
                    ['珊瑚王国', '珊瑚王国是一个水下仙境般的海洋岛屿。岛的周围是壮观的珊瑚礁，各种色彩斑斓的鱼群在其中嬉戏。岛上有一座透明的海底玻璃屋，让人们可以近距离观察海洋生物的美丽和多样性。'],
                    ['风暴之眼', '风暴之眼是一个充满刺激和冒险的海岛。岛上常年风力强劲，形成了无数壮观的风暴和巨浪。登上岛的最高峰，可以感受到狂风的呼啸和惊涛的喧嚣，这是勇者们挑战极限的天堂。'],
                    ['迷失之境', '迷失之境是一个神秘而令人入迷的海岛。岛上有茂密的丛林和隐藏的古代废墟，传说中埋藏着无数宝藏和谜题。只有解开这些谜题的人才能找到正确的道路，离开这个迷失之境。'],
                    ['蜜蜂乐园', '蜜蜂乐园是一个充满甜蜜和快乐的海岛。岛上有无边无际的花海和繁忙的蜜蜂。参观蜂巢，品尝新鲜的蜂蜜，还可亲眼目睹蜜蜂们辛勤工作的景象，为岛上的每个角落带来了欢乐与生机。'],
                    ['神秘洞穴', '神秘洞穴是一个隐藏着无数谜题和宝藏的地方。岛上有许多神奇的洞穴，其中一些通往未知的世界。洞穴中的石壁上刻满了古老的符文，只有能解读这些符文的人才能找到真正的宝藏。'],
                    ['月光海滩', '月光海滩是一个宁静而浪漫的海岛。在夜晚，海滩上的沙子会发出微弱的荧光，形成美丽的月光之路。散步在海滩上，听着海浪的声音，让人感受到宁静和幸福，仿佛置身于一个童话般的世界。'],
                    ['音乐仙境', '音乐仙境是一个充满音乐和欢乐的海岛。岛上有一座古老的音乐神殿，每天都会有美妙的音乐在岛屿上回荡。参与者可以亲自参与音乐创作，与其他音乐爱好者一起共享音乐的魅力。'],
                    ['冰雪王国', '冰雪王国是一个充满魔法和奇迹的海岛。岛上永远被白雪覆盖，形成了壮丽的冰川和冰洞。在这个冰雪世界中，有许多神秘的生物和令人惊叹的冰雕艺术，让人仿佛置身于童话故事中。'],
                    ['热带狂欢', '热带狂欢是一个充满热情和节日气氛的海岛。岛上有一座巨大的嘉年华场地，每年都会举办盛大的狂欢节。参与者可以尽情享受游乐设施、色彩斑斓的游行以及绚丽多彩的焰火表演。'],
                    ['梦幻之境', '梦幻之境是一个充满奇幻和梦境的海岛。岛上有一片神秘的迷雾森林，据说每当月圆之夜，森林中会出现神奇的生物和令人惊叹的景象。在这个梦幻之境中，一切都有可能发生。'],
                    ['夜空之城', '夜空之城是一个浪漫而神秘的海岛。岛上有一座巨大的天文观测塔，让人们可以近距离观察星空的壮丽和神秘。每年的流星雨期间，岛上还会举办浪漫的观星活动，让人们流连忘返。'],
                    ['飞瀑谷', '飞瀑谷是一个壮丽而清新的海岛。岛上有一条巨大的瀑布，水流如同白色绸带从高处飞泻而下。站在瀑布旁，感受到水珠的飞溅和湿润，仿佛能够洗净身心，找到内心的宁静。'],
                    ['神秘海底城', '神秘海底城是一个隐藏着无数秘密和宝藏的海岛。岛下有一座庞大的海底城市，被珊瑚和鱼群环绕。城市中有许多被遗忘的宝藏和古老的文物，等待勇敢者的发现和探索。'],
                    ['翡翠海湾', '翡翠海湾是一个清澈而宜人的海岛。岛上有一片令人惊叹的翡翠色海水，周围是郁郁葱葱的森林。在这片宁静的海湾中，可以享受到宜人的沙滩和温暖的阳光，让人感到平静与幸福。'],
                    ['火山之峰', '火山之峰是一个充满活力和危险的海岛。岛上有一座巍峨的火山，时不时喷发出岩浆和火山灰。登上火山顶端，可以俯瞰整个岛屿和壮观的火山景象，给人带来一种令人兴奋和敬畏的感觉。'],
                    ['天使之羽', '天使之羽是一个宁静而神秘的海岛。岛上有一片细白如羽毛的沙滩，周围环绕着碧蓝的海水。'],
                    //['天使之羽', '天使之羽是一个宁静而神秘的海岛。岛上有一片细白如羽毛的沙滩，周围环绕着碧蓝的海水。在这个天使般的海岛上，可以感受到宁静与和平，仿佛置身于一个远离尘嚣的天堂。'],
                ).map(a => ({ name: a[0], description: a[1] }))
                this.toRecord = (cellValue) => {
                    return { '打卡': cellValue.charAt(0) === '1', '精选': cellValue.charAt(1) === '1', '好事': cellValue.charAt(2) === '1' }
                }
            }

            var day = DATA.activityInfo['航海天数']
            if (day === 0) {
                return '航海未开始'
            }

            var sailorNickname = this.getCellValue({ rowKey, columnKey: '微信昵称' })
            var recordCount = new Array(day).fill('').filter((d, i) => this.toRecord(this.getCellValue({ rowKey, columnKey: `Day${i + 1}` }) || '000')['打卡']).length
            var currentRecord = this.toRecord(this.getCellValue({ rowKey, columnKey: `Day${day}` }) || '000')
            var currentIsland = this.islands[day - 1]

            if (day === 21) {
                var goodCount = new Array(day).fill('').filter((d, i) => this.toRecord(this.getCellValue({ rowKey, columnKey: `Day${i + 1}` }) || '000')['好事']).length

                var text = ``

                 text += `🚢游戏Day${day}，欢迎 ${sailorNickname}
                          来到最终海岛🏝${currentIsland['name']}

                          -----${currentIsland['description']}
                 `
                //if (currentRecord['打卡']) {
                //    text += `
                //        🎉恭喜你完成最后一个任务
                //        -----◻在 🏝${currentIsland['name']} 打卡1次
                //    `
                //} else {
                //    text += `
                //        🎉欢迎在 🏝${currentIsland['name']} 记录下你在本次航行中的所见所得哦
                //    `
                //}
                text += `
                    🏆你已在本次航行中获得以下成就
                    ✅持续行动，复盘打卡${recordCount}次
                    `
                if (goodCount > 0) {
                    text += `✅抓住机会，发生好事${goodCount}次
                    `
                }
                text += `
                    🆕同时解锁一个宝箱
                    -----宝箱里装的是一个金色的船舵和一张空白的藏宝图。愿你在以后的实战探险中，能够随时出发，乘风破浪，时有所获，绘制属于你自己的藏宝图呀~
                `
                return text.split('\n').map(t => t.trim()).join('\n').replace(new RegExp('-----', 'gm'), '     ')
            }

            var week = parseInt((day - 1) / 7 + 1)
            var weekDay = (day - 1) % 7 + 1
            var preRecord = this.toRecord(this.getCellValue({ rowKey, columnKey: `Day${day - 1}` }) || '000')
            var preWeekRecords = day > 7 ? new Array(7).fill(day > 14 ? 8 : 1).map((d, i) => this.toRecord(this.getCellValue({ rowKey, columnKey: `Day${d + i}` }) || '000')) : undefined
            var currentWeekRecords = new Array(7).fill(day > 14 ? 15 : (day > 7 ? 8 : 1)).filter((d, i) => d + i <= day).map((d, i) => this.toRecord(this.getCellValue({ rowKey, columnKey: `Day${d + i}` }) || '000'))
            if (currentWeekRecords.length < 7) {
                currentWeekRecords = currentWeekRecords.concat(new Array(7 - currentWeekRecords.length).fill({}))
            }

            var preIsland = day - 2 < 0 ? undefined : this.islands[day - 2]
            var nextIsland = day < this.islands.length ? this.islands[day] : undefined

            var finishedMissions = []
            var newMissions = []

            if (preIsland && preRecord['打卡']) {
                if (isYesterdayRemindedBeforeRecord) {
                    finishedMissions.push(`在 🏝${preIsland.name} 复盘记录1次`)
                    if (preRecord['精选']) {
                        finishedMissions.push(`在 🏝${preIsland.name} 日志被精选`)
                    }
                    if (preRecord['好事']) {
                        finishedMissions.push(`在 🏝${preIsland.name} 发生好事`)
                    }
                    if (weekDay === 1) {
                        if (preWeekRecords && preWeekRecords.filter(record => record['打卡']).length === 4) {
                            finishedMissions.push(`持续行动，第${week - 1}周复盘记录4次`)
                        }
                    } else {
                        if (currentWeekRecords.filter(record => record['打卡']).length === (currentRecord['打卡'] ? 5 : 4)) {
                            finishedMissions.push(`持续行动，第${week}周复盘记录4次`)
                        }
                    }
                }
            }
            if (currentRecord['打卡']) {
                finishedMissions.push(`在 🏝${currentIsland.name} 复盘记录1次`)
                if (currentWeekRecords.filter(record => record['打卡']).length === 4) {
                    finishedMissions.push(`持续行动，第${week}周复盘记录4次`)
                }
                if(recordCount === 12){
                    finishedMissions.push(`持续行动，21天复盘记录12次`)
                }
                if (nextIsland) {
                    newMissions.push(`在下一个目的地\n-----🏝${nextIsland.name} 复盘记录1次`)
                }
            } else {
                newMissions.push(`在 🏝${currentIsland.name} 复盘记录1次`)
            }

            var text = ''

            if (day === 1) {
                text += `🚢欢迎 ${sailorNickname} 加入本次航海游戏~
                         -----游戏第${day}天，你已来到第一座海岛🏝${currentIsland['name']}
                         🏝${currentIsland['description']}
                `
            } else {
                text += `🚢游戏Day${day}，欢迎 ${sailorNickname} 来到
                         🏝${currentIsland['name']}
                `
                // text += `🚢游戏Day${day}，欢迎 ${sailorNickname} 来到
                //          🏝${currentIsland['name']}

                //          -----${currentIsland['description']}
                // `
            }

            if(recordCount < 12&&(21-day)<(12-recordCount-(currentRecord['打卡']?0:1))){
            }else if(false){

            if (finishedMissions.length === 0) {
                if (newMissions.length > 0) {
                    text += `
                        🆕你已解锁以下挑战
                        ${newMissions.map(mission => `◻${mission}`).join('\n')}
                    `
                }
            } else {
                text += `
                    🎉恭喜你完成以下挑战
                    ${finishedMissions.map(mission => `✅${mission}`).join('\n')}
                `
                if (newMissions.length > 0) {
                    text += `
                        🆕同时解锁以下挑战
                        ${newMissions.map(mission => `◻${mission}`).join('\n')}
                    `
                }
            }

            }


            text += `
                🎁并获得以下航海补给包
                ✅用RPA对图片高效去重
                ✅用RPA进行小红书养号
                https://pskixpl4x5.feishu.cn/docx/HUCpd1CMeofiZhxr0ppcOfb7nvb#LQK8d14TioqDdFxvDxqcoW1Mn1d
            `
            if(recordCount < 12&&(21-day)<(12-recordCount-(currentRecord['打卡']?0:1))){
            }else if(recordCount<12||(recordCount===12&&currentRecord['打卡'])){
            text += `
                🗺当前主线任务
                ${recordCount < 12 ? '◻' : '✅'}持续行动，21天复盘记录12次
                -----${new Array(Math.min(recordCount, 12)).fill('■').join('')}${new Array(Math.max(12 - recordCount, 0)).fill('□').join('')}
            `
            }else if(recordCount<15||(recordCount===15&&currentRecord['打卡'])){
            text += `
                🗺当前主线任务
                ${recordCount < 15 ? '◻' : '✅'}持续行动，21天复盘记录15次
                -----${new Array(Math.min(recordCount, 15)).fill('■').join('')}${new Array(Math.max(15 - recordCount, 0)).fill('□').join('')}
            `
            }else if(recordCount<21||(recordCount===21&&currentRecord['打卡'])){
            text += `
                🗺当前主线任务
                ${recordCount < 21 ? '◻' : '✅'}持续行动，21天复盘记录21次
                -----${new Array(Math.min(recordCount, 21)).fill('■').join('')}${new Array(Math.max(21 - recordCount, 0)).fill('□').join('')}
            `
            }
            // text += `
            //     🗺当前游戏挑战进度

            //     🏆主线任务
            //     ${recordCount < 12 ? '◻' : '✅'}持续行动，21天复盘打卡12次
            //     -----${new Array(Math.min(recordCount, 12)).fill('■').join('')}${new Array(Math.max(12 - recordCount, 0)).fill('□').join('')}
            // `
            // text += `◻第一阶段：确定 1 个旅游目的地 & 匹配供应商
            // `
            // text += `
            //     🔍支线任务
            //     ${currentWeekRecords.filter(record => record['打卡']).length < 4 ? '◻' : '✅'}持续行动，第${week}周复盘打卡4次
            //     -----${currentWeekRecords.map((record, i) => i < weekDay ? (record['打卡'] ? '■' : (i === (weekDay - 1) ? '□' : '☒')) : '□').join('')}
            // `

            if (day === 1) {
                text += `
                    📌常驻NPC辅助
                    👥交流群：大船成员们会非常乐意解答你的 #举手 提问
                    📝航海日志：在日志里记录下你遇到的问题，{志愿者微信昵称}会帮你寻找解决办法。

                    🌀传送门`
//                 if (DATA.activityInfo['航海名称'] === '小红书境外游') {
//                     text += `
//                         📚打卡首页-浏览其他船员日志获得灵感 https://i.shengcaiyoushu.com/t/BOA7IRFt
//                         📚实战手册-按照航线行进不偏航 https://i.shengcaiyoushu.com/t/BOA7OPFt
//                         `
//                 } else {
                    text += `
                        📚打卡首页-浏览其他船员日志获得灵感 https://scys.com/activity/task?activity_id=${DATA.activityInfo['id']}
                        📚实战手册-按照航线行进不偏航 https://scys.com/activity/documents?id=${DATA.activityInfo['id']}
                    `
//                }
            }
            return text.split('\n').map(t => t.trim()).join('\n').replace(new RegExp('-----', 'gm'), '     ')
        }

        updateCopyBtns() {
            this.bodyRowCellsManagers.getItems().forEach(manager => {
                const rowKey = manager.key

                const copyBtnsCellElement = manager.getItem({ itemKey: '话术复制' })
                if (copyBtnsCellElement.copyBtnsManager) {
                    copyBtnsCellElement.lastElementChild.remove()
                } else {
                    copyBtnsCellElement.style.overflow = 'hidden'
                    copyBtnsCellElement.addEventListener('mouseenter', () => {
                        copyBtnsCellElement.style.overflow = 'visible'
                        copyBtnsCellElement.firstElementChild.style.height = null
                    })
                    copyBtnsCellElement.addEventListener('mouseleave', () => {
                        copyBtnsCellElement.style.overflow = 'hidden'
                        copyBtnsCellElement.firstElementChild.style.height = '33px'
                    })
                }
                const copyBtnsELement1 = document.createElement('div')
                copyBtnsELement1.style.height = "33px"
                this.createCopyBtnElements({ rowKey }).forEach(copyBtnElement => {
                    copyBtnsELement1.appendChild(copyBtnElement)
                })
                copyBtnsCellElement.appendChild(copyBtnsELement1)
                copyBtnsCellElement.copyBtnsManager = new MD.Manager.ElementListManager({ key: rowKey, parentElement: copyBtnsELement1 })

                const cardElement = this.cardsManager.getItem({ itemKey: rowKey })
                if (cardElement.copyBtnsManager) {
                    cardElement.lastElementChild.remove()
                } else {
                    cardElement.style.maxHeight = 'unset'
                    cardElement.style.marginRight = '10px'
                    cardElement.style.overflow = 'visible'
                    cardElement.querySelector('div.middle > div:nth-child(1)').addEventListener('click', () => {
                        MD.UI.copyText(this.getCellValue({ rowKey, columnKey: '微信昵称' }))
                    })
                }
                const copyBtnsELement2 = document.createElement('div')
                copyBtnsELement2.className = 'md-cardbtns'
                copyBtnsELement2.style.height = null
                copyBtnsELement2.style.alignItems = null
                copyBtnsELement2.style.flexWrap = 'wrap'
                copyBtnsELement2.style.marginTop = '-15px'
                this.createCopyBtnElements({ rowKey }).forEach(copyBtnElement => {
                    copyBtnsELement2.appendChild(copyBtnElement)
                })
                cardElement.appendChild(copyBtnsELement2)
                cardElement.copyBtnsManager = new MD.Manager.ElementListManager({ key: rowKey, parentElement: copyBtnsELement2 })
            })
        }

        show() {
            MD.UI.show(this.tableElement.parentElement)
        }
        hide() {
            MD.UI.hide(this.tableElement.parentElement)
        }

        initStyles(styles) {
            MD.UI.addStyle(`
                    .md-cardbtns{
                        display: flex;
                        flex-direction:row;
                        flex-wrap:wrap;
                        margin: -15px 0 0 0;
                        padding:0 10px 5px 10px;
                    }
                    .md-cardbtn{
                        position: relative;
                        background: #36a590;
                        color: white;
                        font-size: 12px;
                        padding: 2.5px 5px;
                        margin: 2.5px;
                        height: fit-content;
                        text-align: center;
                    }
                    .md-cardbtn:hover {
                        background: #26816f;
                        cursor: pointer;
                    }
                    .md-cardbtn:active {
                        background: #1c6050;
                        outline: none;
                    }
                    .md-cardbtn-img{
                        height: 20px;
                        width: 20px;
                        margin: 2.5px 5px;
                    }
                `)
            super.initStyles(styles)
        }

        createCopyBtnElements({ rowKey }) {
            const textBtnRender = ({ btnName, content, rowKey }) => {
                var btn = document.createElement('div')
                btn.className = 'md-cardbtn'
                btn.innerHTML = btnName
                return btn
            }
            const imgBtnRender = ({ btnName, content, rowKey }) => {
                var btn = document.createElement('div')
                btn.style.position = 'relative'
                btn.innerHTML = `<img class="md-cardbtn-img" src="${content}"></img>`
                return btn
            }
            return this.copyBtnConfigsManager.getItems().map(({ key, btnName, content, btnType }) => {
                const btnRender = btnType === 'img' ? imgBtnRender : textBtnRender
                const btn = btnRender({ btnName, content, rowKey })
                btn.key = key
                if (key === '游戏任务版打卡提醒：昨天打卡前提醒的' || key === '游戏任务版打卡提醒：昨天打卡后提醒的') {
                    MD.UI.addEventListeners(btn, {
                        onClick: () => {
                            MD.UI.copyText(this.getIslandGameText({ rowKey, isYesterdayRemindedBeforeRecord: key === '游戏任务版打卡提醒：昨天打卡前提醒的' }))
                        },
                        onRightClick: () => {
                            const { key: itemKey } = btn
                            MD.UI.createHoverMenu({
                                element: btn,
                                isShowNow: true,
                                buttons: Array.of(
                                    ['前移按钮', () => {
                                        this.copyBtnConfigsManager.moveItem({ itemKey, direction: -1 })
                                    }, (btn) => {
                                        btn.hidden = this.copyBtnConfigsManager.getItemIndex(itemKey) === 0
                                    }],
                                    ['后移按钮', () => {
                                        this.copyBtnConfigsManager.moveItem({ itemKey, direction: 1 })
                                    }, (btn) => {
                                        btn.hidden = this.copyBtnConfigsManager.getItemIndex(itemKey) === this.copyBtnConfigsManager.getItems().length - 1
                                    }],
                                    ['添加按钮', () => {
                                        this.copyBtnConfigsManager.insertItem({
                                            item: {
                                                key: itemKey,
                                                btnName: '双击按钮编辑',
                                                content: '',
                                                btnType: 'text'
                                            }, beforeItemKey: itemKey
                                        })
                                        this.updateCopyBtns()
                                    }]
                                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                            });
                        }
                    })
                } else {
                    MD.UI.addEventListeners(btn, {
                        onClick: () => {
                            if (btnType === 'img') {
                                window.open(content)
                            } else {
                                var values = {}
                                Object.keys(this.volunteerInfo).forEach(key => {
                                    values[`志愿者${key}`] = this.volunteerInfo[key]
                                })
                                Object.keys(this.activityInfo).forEach(key => {
                                    values[key] = this.activityInfo[key]
                                })
                                this.columnsManager.getItemKeys().forEach(columnKey => {
                                    values[columnKey] = this.getCellValue({ rowKey, columnKey })
                                })
                                var copyContent = content
                                Object.keys(values).forEach(key => {
                                    copyContent = copyContent.replace(new RegExp(`{${key}}`, 'gm'), values[key])
                                })
                                MD.UI.copyText(copyContent)
                            }
                        },
                        onDblClick: () => {

                            MD.UI.createPopup((popup) => {
                                popup.innerHTML = `
                                    <div style="font-size: 13px;">
                                        <p>
                                            <span style="margin-right: 10px;">按钮类型：</span>
                                            <input type="radio" id="link" name="btnType" value="link">
                                            <label for="link">链接</label>
                                            <input type="radio" id="text" name="btnType" value="text" style="margin-left:10px">
                                            <label for="text">话术</label>
                                            <input type="radio" id="img" name="btnType" value="img" style="margin-left:10px">
                                            <label for="img">图片</label>
                                        </p>
                                        <p>
                                            <span style="margin-right: 10px;">按钮名字：</span>
                                            <input id="btnName" type="text" style="border: 1px solid #ccc;height: 20px;padding: 0 10px;">
                                        </p>
                                        <p style="display: flex;">
                                            <span style="margin-right: 10px;width: 68px;"><span>按钮内容：</span><br><br><span style="color: #aaa;">文字类型填需要复制的内容，其它类型填链接</span></span>
                                            <textarea id="content" style="width: 300px;height: 100px;padding: 10px;border: solid 1px #ccc;"></textarea>
                                        </p>
                                        <p style="padding: 10px;text-align: center;" id="confirm" class="md-cardbtn">保存按钮设置</p>
                                    </div>
                                `
                                const radios = Array.from(popup.querySelectorAll('[name="btnType"]'))
                                radios.forEach(radio => {
                                    radio.checked = radio.value === btnType
                                })

                                const btnNameInput = popup.querySelector('#btnName')
                                btnNameInput.value = btnName

                                const contentTextarea = popup.querySelector('#content')
                                contentTextarea.value = content || ''
                                contentTextarea.addEventListener('contextmenu', e => {
                                    e.preventDefault()
                                    MD.UI.createHoverMenu({
                                        element: popup.querySelector('div > p:nth-child(2)'),
                                        isShowNow: true,
                                        scrollable: true,
                                        buttons: this.columnsManager.getItemKeys().concat(Object.keys(this.volunteerInfo).map(key => `志愿者${key}`)).concat(Object.keys(this.activityInfo)).filter(key => key.charCodeAt(0) > 127).map(key => ({
                                            name: key, onClick: () => {
                                                contentTextarea.value = contentTextarea.value.substring(0, contentTextarea.selectionStart) + `{${key}}` + contentTextarea.value.substring(contentTextarea.selectionStart);
                                            }
                                        }))
                                    });

                                });

                                popup.querySelector('#confirm').addEventListener('click', () => {
                                    if (!btnNameInput.value || !contentTextarea.value) {
                                        return
                                    }
                                    var item = this.copyBtnConfigsManager.getItem({ itemKey: key })
                                    item.btnName = btnNameInput.value
                                    item.content = contentTextarea.value
                                    item.btnType = radios.find(radio => radio.checked).value
                                    this.copyBtnConfigsManager.updateItem({ item })
                                    this.updateCopyBtns()
                                    popup.remove()
                                });
                            })

                            return


                            MD.UI.createPopup((popup) => {
                                popup.innerHTML = `
                                    <div style="font-size: 13px;">
                                        <p>
                                            <span style="margin-right: 10px;">按钮类型：</span>
                                            <input type="radio" id="text" name="btnType" value="text">
                                            <label for="text">话术</label>
                                            <input type="radio" id="img" name="btnType" value="img" style="margin-left:10px">
                                            <label for="img">图片</label>
                                        </p>
                                        <p>
                                            <span style="margin-right: 10px;">按钮名字：</span>
                                            <input id="btnName" type="text" style="border: 1px solid #ccc;height: 20px;padding: 0 10px;">
                                        </p>
                                        <p style="display: flex;">
                                            <span style="margin-right: 10px;width: 68px;"><span>话术内容：</span><br><br><span style="color: #aaa;">右键编辑框可插入表格内容</span></span>
                                            <textarea id="content" style="width: 300px;height: 100px;padding: 10px;border: solid 1px #ccc;"></textarea>
                                        </p>
                                        <p style="padding: 10px;text-align: center;" id="confirm" class="md-cardbtn">保存按钮设置</p>
                                    </div>
                                `

                                const imgRadio = popup.querySelector('#img')
                                imgRadio.checked = btnType === 'img'
                                popup.querySelector('#text').checked = !imgRadio.checked

                                const btnNameInput = popup.querySelector('#btnName')
                                btnNameInput.value = btnName

                                const contentTextarea = popup.querySelector('#content')
                                contentTextarea.value = content
                                contentTextarea.addEventListener('contextmenu', e => {
                                    e.preventDefault()
                                    MD.UI.createHoverMenu({
                                        element: popup.querySelector('div > p:nth-child(2)'),
                                        isShowNow: true,
                                        scrollable: true,
                                        buttons: this.columnsManager.getItemKeys().concat(Object.keys(this.volunteerInfo).map(key => `志愿者${key}`)).concat(Object.keys(this.activityInfo)).filter(key => key.charCodeAt(0) > 127).map(key => ({
                                            name: key, onClick: () => {
                                                contentTextarea.value = contentTextarea.value.substring(0, contentTextarea.selectionStart) + `{${key}}` + contentTextarea.value.substring(contentTextarea.selectionStart);
                                            }
                                        }))
                                    });

                                });

                                popup.querySelector('#confirm').addEventListener('click', () => {
                                    if (!btnNameInput.value || !contentTextarea.value) {
                                        return
                                    }
                                    var item = this.copyBtnConfigsManager.getItem({ itemKey: key })
                                    item.btnName = btnNameInput.value
                                    item.content = contentTextarea.value
                                    item.btnType = imgRadio.checked ? 'img' : 'text'
                                    this.copyBtnConfigsManager.updateItem({ item })
                                    this.updateCopyBtns()
                                    popup.remove()
                                });
                            })
                        },
                        onRightClick: () => {
                            const { key: itemKey } = btn
                            MD.UI.createHoverMenu({
                                element: btn,
                                isShowNow: true,
                                buttons: Array.of(
                                    ['前移按钮', () => {
                                        this.copyBtnConfigsManager.moveItem({ itemKey, direction: -1 })
                                    }, (btn) => {
                                        btn.hidden = this.copyBtnConfigsManager.getItemIndex(itemKey) === 0
                                    }],
                                    ['后移按钮', () => {
                                        this.copyBtnConfigsManager.moveItem({ itemKey, direction: 1 })
                                    }, (btn) => {
                                        btn.hidden = this.copyBtnConfigsManager.getItemIndex(itemKey) === this.copyBtnConfigsManager.getItems().length - 1
                                    }],
                                    ['添加按钮', () => {
                                        this.copyBtnConfigsManager.insertItem({
                                            item: {
                                                key: itemKey,
                                                btnName: '双击按钮编辑',
                                                content: '',
                                                btnType: 'text'
                                            }, beforeItemKey: itemKey
                                        })
                                        this.updateCopyBtns()
                                    }],
                                    ['删除按钮', () => {
                                        this.copyBtnConfigsManager.removeItem({ itemKey })
                                    }, btn => {
                                        // btn.hidden = !this.copyBtnConfigsManager.isNewItem({ itemKey })
                                    }]
                                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                            });
                        }
                    })
                }
                return btn
            })
        }

        switchDisplayMode(displayMode) {
            if (!displayMode) {
                displayMode = this._cache.get('displayMode')
                if (!displayMode) {
                    displayMode = 'card'
                } else {
                    displayMode = displayMode === 'card' ? 'table' : 'card'
                }
                this._cache.set('displayMode', displayMode)
            } else if (displayMode === 'current') {
                displayMode = this._cache.get('displayMode')
            }

            if (displayMode === 'table') {
                this.show()
                this.cardsManager.hide()
            } else if (displayMode === 'card') {
                this.hide()
                this.cardsManager.show()
            } else {
                this.hide()
                this.cardsManager.hide()
            }
            return displayMode
        }

        onBodyCellClick({ rowKey, columnKey }) {
            if (columnKey === '话术复制') {
                return true
            } else if (columnKey === '星球头像') {
                this.cardsManager.getItem({ itemKey: rowKey }).querySelector('div.header > div.left.v-row > img').click()
                // MD.UI.copyImage({ element: this.bodyRowCellsManagers.getItem({ itemKey: rowKey }).getItem({ itemKey: columnKey }).querySelector('img') })
                return true
            } else {
                return false
            }
        }

        onBodyCellDblClick({ rowKey, columnKey }) {
            // if (this.uneditableColumns.includes(columnKey)) {
            //     return true
            // } else
            if (columnKey === '星球头像') {
                this.cardsManager.getItem({ itemKey: rowKey }).querySelector('div.header > div.left.v-row > img').click()
                return true
            } else if (columnKey === '上船进度') {
                this.cardsManager.getItem({ itemKey: rowKey }).querySelector('div.middle > div:nth-child(5) > span:nth-child(2)').click()
                MD.UI.waitForElement({ selector: '#app > div.modal > div > div > div.button-remark > div' }).then(confirmBtn => {
                    confirmBtn.addEventListener('click', () => {
                        const text = Array.from(document.querySelectorAll('#app > div.modal > div > div > div.content-expand > div.option')).find(option => option.querySelector('.check').style.display !== 'none').querySelector('span').textContent;
                        this.datasManager.updateItem({ itemKey: rowKey, values: [{ valueKey: columnKey, value: text }] })
                        this.updateTable({ rowKey, columnKey })
                    })
                }).catch(reson => {
                    MD.UI.showToast('如果修改后表格数据没更新，可手动刷新页面试试', 3000)
                });
                return true
            } else if (columnKey === '备注') {
                this.cardsManager.getItem({ itemKey: rowKey }).querySelector('div.middle > div:nth-child(6) > div').click()
                MD.UI.waitForElement({ selector: '#app > div.modal > div > div > div.button-remark > div' }).then(confirmBtn => {
                    confirmBtn.addEventListener('click', () => {
                        const text = document.querySelector('#app > div.modal > div > div > div.wrapper > textarea').value
                        this.datasManager.updateItem({ itemKey: rowKey, values: [{ valueKey: columnKey, value: text }] })
                        this.updateTable({ rowKey, columnKey })
                    })
                }).catch(reson => {
                    MD.UI.showToast('如果修改后表格数据没更新，可手动刷新页面试试', 3000)
                });
                return true
            } else if (columnKey === '打卡提醒' && cellValue === '待提醒') {
                this.cardsManager.getItem({ itemKey: rowKey }).querySelector('#scroll > div.volunteer-list > div:nth-child(1) > div.bottom.v-row > div.right2').click()
                this.datasManager.updateItem({ itemKey: rowKey, values: [{ valueKey: columnKey, value: '已提醒' }] })
                this.updateTable({ rowKey, columnKey })
                return true
            } else {
                return false
            }
        }

        onBodyCellRightClick({ rowKey, columnKey }) {

        }

        onUpdateBodyCell({ cellElement, rowKey, columnKey, cellValue }) {
            if (columnKey === '星球头像') {
                cellElement.innerHTML = `<img style="width: 28px;height: 28px;border-radius: 50%;" src="${cellValue}"></img>`
                return true
            } else if (columnKey === '已打卡天数') {
                var style = 'color:'
                if (cellValue >= 12) {
                    style += 'green'
                } else {
                    var d = this.getCellValue({ rowKey, columnKey: '可打卡天数' }) - this.getCellValue({ rowKey, columnKey: '距上岸天数' })
                    style += (0 <= d && d <= 3) ? 'red' : '#999'
                }
                cellElement.innerHTML = `<span style="${style}">${cellValue}</span>`
                return true
            } else if (columnKey === '话术复制') {
                return true
            } else if (columnKey === '备注') {
                cellElement.innerHTML = `<div class="break-line" style="height: 33px;max-width: 100px;">${cellValue}</div>`
                return true
            } else if (this.dayColumnPattern.test(columnKey)) {
                if (this.recordDisplay === 'detail') {
                    cellElement.innerHTML = `<div class="break-line" style="max-width: 300px;">${this.datasManager.getItemValue({ itemKey: rowKey, valueKey: `record-${columnKey}` }) || ''}</div>`
                } else {
                    if (cellValue === '111') {
                        cellElement.innerHTML = `
                            <span style="
                                background: #36a590;
                                color: white;
                                padding: 2.5px 5px;
                                border-radius: 2.5px;
                            ">精选</span>
                            <span style="
                                color: white;
                                padding: 2.5px 5px;
                                border-radius: 2.5px;
                                margin-left: 5px;
                                background: #f39d3d;
                            ">好事</span>
                        `
                    } else if (cellValue === '110') {
                        cellElement.innerHTML = `
                            <span style="
                                background: #36a590;
                                color: white;
                                padding: 2.5px 5px;
                                border-radius: 2.5px;
                            ">精选</span>
                        `
                    } else if (cellValue === '101') {
                        cellElement.innerHTML = `
                            <span style="
                                color: white;
                                padding: 2.5px 5px;
                                border-radius: 2.5px;
                                background: #f39d3d;
                            ">好事</span>
                        `
                    } else if (cellValue === '100') {
                        cellElement.innerHTML = `<span>✔️</span>`
                    } else {
                        cellElement.innerHTML = `<span>❌</span>`
                    }
                }
                return true
            } else {
                if (cellValue === '是' || cellValue === '否') {
                    cellElement.innerHTML = `<span>${cellValue === '是' ? '✔️' : '❌'}</span>`
                } else {
                    cellElement.innerHTML = `<span>${cellValue}</span>`
                }
                return true
            }
        }
    }

    const DATA = new function () {
        this.volunteerInfo = {}
        this.sailorInfos = []
        this.activityInfo = {}
    }

    const VIEW = new function () {
        this.sailorInfoTable = undefined
    }

    const REQUEST = window.REQUEST = new function () {
        this.sailorInfos = new MD.XHR.ProxyRequest('https://scys.com/search/activity/volunteer/user/ajax')
        this.sailorRecords = new MD.XHR.ProxyRequest('https://scys.com/search/activity/volunteer/user/remark/ajax')
    }

    REQUEST.sailorInfos.onload(request => {

//         if (!REQUEST.requester) {
//             var moduleLink = 'https://scys.com/assets/index-16f8e9dd.js'
//             if (MD.Storage.global.get('wrongModule') !== moduleLink) {
//                 import(moduleLink).then((module) => {
//                     if (!document.querySelector('#app').innerHTML) {
//                         MD.Storage.global.set('wrongModule', moduleLink)
//                         window.location.reload()
//                         return
//                     }
//                     REQUEST.requester = module[Object.keys(module).find(key => {
//                         var value = module[key]
//                         return value.defaults && value.defaults.baseURL && value.defaults.baseURL
//                     })]
//                     MD.XHR.ProxyRequest.initRequester({
//                         post(url, params) {
//                             url = url.substring(REQUEST.requester.defaults.baseURL.length)
//                             return REQUEST.requester.post(url, params)
//                         },
//                         get(url) {
//                             url = url.substring(REQUEST.requester.defaults.baseURL.length)
//                             return REQUEST.requester.get(url)
//                         }
//                     })
//                     REQUEST.sailorRecords.send({ "page": 1, "form": { "activity_id": `${activity.id}`, "stage_id": "", "remark": "" } })
//                 }).catch((error) => {
//                         MD.Storage.global.set('wrongModule', moduleLink)
//                         window.location.reload()
//                         console.error('Error loading module:', error);
//                     });
//             }
//         }

        const { items, extra: { stage, volunteer, user, activity, form, stageLL } } = JSON.parse(request.responseText).data

        const toSailorInfo = ({ group_number, wx_nickname, name, avatar, data: { rec_group_name } }) => ({
            '星球编号': group_number,
            '星球昵称': name,
            '星球头像': avatar || 'https://scys.com/images/activity/task/pic_avatar.png',
            '微信昵称': wx_nickname,
            '群昵称': rec_group_name,
        })

        var startTime
        var endTime
        if (stageLL && stageLL[0] && stageLL['gmt_start']) {
            startTime = stageLL[0]['gmt_start']
            endTime = stageLL[stageLL.length - 1]['gmt_expire']
        } else {
            const date = new Date(activity['gmt_1'] * 1000);
            date.setDate(date.getDate() + 1);
            date.setHours(0, 0, 0, 0);
            startTime = Math.floor(date.getTime() / 1000)
            date.setDate(date.getDate() + 21);
            endTime = Math.floor(date.getTime() / 1000)
        }
        const currentTimestamp = Math.floor(Date.now() / 1000)

        const currentDay = stage.name ? stage.idx + 1 : (currentTimestamp > endTime ? 21 : 0)

        const isSailing = startTime <= currentTimestamp && currentTimestamp <= endTime

        const wxFormItemId = form.find(i=>i.title==='联系方式').id
        const mobileFormItemId = form.find(i=>i.title==='手机号').id

        DATA.volunteerInfo = toSailorInfo(user[volunteer])

        DATA.sailorInfos = items.filter(({ xq_group_number }) => xq_group_number !== DATA.volunteerInfo['星球编号']).map(({ is_submit, xq_group_number, state, remark, today_remind, comment, project_cnt, info = {} }, index) => {
            const sailorInfo = toSailorInfo(user[xq_group_number])
            sailorInfo.key = xq_group_number
            sailorInfo['默认序号'] = index + 1
            sailorInfo['微信号'] = info[wxFormItemId]
            sailorInfo['手机号'] = info[mobileFormItemId]
            sailorInfo['上船进度'] = state === '未报名' ? '船员退出' : (remark ? remark : '双击编辑')
            sailorInfo['备注'] = comment
            sailorInfo['打卡提醒'] = isSailing ? ((remark === '无需提醒' || state === '未报名') ? '无需提醒' : (today_remind ? '已提醒' : '待提醒')) : ''
            sailorInfo['已打卡天数'] = project_cnt
            sailorInfo['距上岸天数'] = Math.max(0, 12 - project_cnt)
            sailorInfo['可打卡天数'] = isSailing ? Math.max(0, 21 - currentDay + (is_submit ? 0 : 1)) : ''
            sailorInfo['可休息天数'] = isSailing ? Math.max(0, sailorInfo['可打卡天数'] - sailorInfo['距上岸天数']) : ''
            sailorInfo['是否已打卡'] = isSailing ? (is_submit ? '是' : '否') : ''
            var groupNickname = sailorInfo['群昵称'].split('-')
            sailorInfo['航海期数'] = groupNickname[groupNickname.length - 1].substring(3)
            sailorInfo['所在城市'] = groupNickname[groupNickname.length - 2]
            return sailorInfo
        })

        DATA.activityInfo = {
            '航海名称': activity.name,
            '航海天数': currentDay,
            '小组报名人数': DATA.sailorInfos.length,
            '小组退出人数': DATA.sailorInfos.filter(sailorInfo => sailorInfo['上船进度'] === '船员退出').length,
            '小组当前人数': DATA.sailorInfos.length - DATA.sailorInfos.filter(sailorInfo => sailorInfo['上船进度'] === '船员退出').length,
            '小组上岸人数': DATA.sailorInfos.filter(sailorInfo => sailorInfo['已打卡天数'] >= 12).length,
            '小组今天必须打卡人数': isSailing ? DATA.sailorInfos.filter(sailorInfo => sailorInfo['可休息天数'] === 0).length : '',
            startTime,
            endTime,
            isSailing,
            id: activity.id,
        }

        initViews()

    })

    var updateSailorRecords = undefined

    REQUEST.sailorRecords.onload(request => {
        var { items, extra: { form } } = JSON.parse(request.responseText).data
        if (!items || !form) { return }
        form = Array.from(form).sort((a, b) => a.id > b.id ? 1 : -1)
        updateSailorRecords = () => {
            items.forEach(({ stage_idx, stage_id, xq_group_number, is_digested, is_good, data }) => {
                var value = '1'
                value += is_digested ? '1' : '0'
                value += is_good ? '1' : '0'
                if (stage_idx === undefined) {
                    stage_idx = 1
                } else {
                    stage_idx = stage_idx + 1
                }
                var baseFormId = form.length > 7 ? form[(stage_idx - 1) * 7].id : form[0].id
                VIEW.sailorInfoTable.datasManager.updateItemValue({ itemKey: xq_group_number, valueKey: `record-Day${stage_idx}`, value: `${data[`${baseFormId + 1}`]}\n\n${data[baseFormId + 2]}\n\n${data[baseFormId + 4]}\n\n${data[baseFormId + 5]}` })
                VIEW.sailorInfoTable.datasManager.updateItemValue({ itemKey: xq_group_number, valueKey: `Day${stage_idx}`, value })
                VIEW.sailorInfoTable.datasManager.updateItemValue({ itemKey: xq_group_number, valueKey: `stageId-Day${stage_idx}`, stage_id })
                VIEW.sailorInfoTable.localValuesManager.updateItemValue({ itemKey: `${xq_group_number}&Day${stage_idx}`, valueKey: 'value', value })
            })
            VIEW.sailorInfoTable.updateTable()
        }
        if (VIEW.sailorInfoTable) {
            updateSailorRecords()
            updateSailorRecords = undefined
        }
    })

    function initViews() {
        MD.UI.waitForElement({ selector: '.volunteer-header' }).then(headerElement => {
            var activityInfoDiv = headerElement.querySelector('#activityInfo')
            if (!activityInfoDiv) {
                headerElement.style.flexDirection = 'row'
                headerElement.style.alignItems = 'center'
                headerElement.style.padding = '10px'
                Array.from(headerElement.children).forEach(e => { e.style.margin = '0 10px' })
                headerElement.insertAdjacentHTML('beforeend', `
                    <div id="activityInfo" style="display: flex;flex-direction: row;font-size: 13px;color: rgba(0,0,0,.65);">
                            <div>小组报名人数：${DATA.activityInfo['小组报名人数']}</div>
                            <div style="margin-top: 10px;">小组退出人数：${DATA.activityInfo['小组退出人数']}</div>
                            <div style="margin-top: 10px;">小组当前人数：${DATA.activityInfo['小组当前人数']}</div>
                    </div>
                `)
                activityInfoDiv = headerElement.lastElementChild
            }
            activityInfoDiv.innerHTML = Array.of('小组报名人数', '小组退出人数', '小组当前人数', '小组上岸人数', '小组今天必须打卡人数').map(key => DATA.activityInfo[key] === '' ? '' : `<span style="margin:0 10px">${key.substring(2)}：${DATA.activityInfo[key]}</span>`).join('\n')

        })
        MD.UI.waitForElement({ selector: '.cards-volunteer' }).then(firstCardElement => {

            var tableElement = document.querySelector('#md-table')
            if (!tableElement) {
                document.querySelector('#scroll').insertAdjacentHTML('beforeend', `
                        <div id="md-table-wrapper"><table id="md-table"></table></div>
                    `)
                tableElement = document.querySelector('#md-table')

                initStyles()
                initTabBtns()
            } else {
                tableElement.innerHTML = ''
            }

            const reload = () => {
                tableElement.innerHTML = ''
                VIEW.sailorInfoTable = new SailorInfoTable({
                    tableElement,
                    cardsElement: document.querySelector('.cards-volunteer').parentElement,
                    sailorInfos: DATA.sailorInfos,
                    activityInfo: DATA.activityInfo,
                    volunteerInfo: DATA.volunteerInfo,
                    reload
                })
                if (updateSailorRecords) {
                    updateSailorRecords()
                }
            }
            reload()
        })
    }

    function initTabBtns() {

        MD.UI.waitForElement({ selector: '#scroll > div.tabs > div > div:nth-child(1)' }).then(tabElement => {
            tabElement.parentElement.parentElement.style.zIndex = 4;
            tabElement.parentElement.style.position = 'relative';
            tabElement.addEventListener('click', () => VIEW.sailorInfoTable.switchDisplayMode('current'))
            Array.from(tabElement.parentElement.children).forEach((e, i) => {
                if (i > 0) {
                    e.addEventListener('click', () => {
                        VIEW.sailorInfoTable.switchDisplayMode('hidden')
                        if (e.textContent.includes('组员作业')) {
                            function updateWxNickname() {
                                MD.UI.waitForElement({ selector: '.task-card' }).then(firstTaskCard => {
                                    Array.from(firstTaskCard.parentElement.children).forEach(cardElement => {
                                        const nameSpan = cardElement.querySelector('div.name > span')
                                        if (DATA.sailorInfos) {
                                            const sailorInfo = DATA.sailorInfos.find(sailorInfo => sailorInfo['星球昵称'] === nameSpan.textContent)
                                            if (sailorInfo) {
                                                nameSpan.innerHTML = `${nameSpan.textContent}（${VIEW.sailorInfoTable.getCellValue({ rowKey: sailorInfo['星球编号'], columnKey: '微信昵称' })}）`
                                            }
                                        }
                                    })
                                    MD.UI.waitForElement({ selector: '#scroll > div:nth-child(2) > div > div.inline-block.select > div.popper > div > div:nth-child(1)' }).then(firstOption => {
                                        Array.from(firstOption.parentElement.children).forEach(option => {
                                            option.addEventListener('click', () => {
                                                setTimeout(() => {
                                                    updateWxNickname()
                                                }, 200);
                                            })
                                        })
                                    })
                                })
                            }
                            updateWxNickname()
                        }
                    })
                }
            })
            tabElement.parentElement.insertAdjacentHTML('afterend', `
                <div style="display: flex;border-bottom: 1px solid #eee;padding: 5px 15px;">
                    <span style="color: rgba(0,0,0,.85);font-size: 13.5px;padding-top: 3px;">常用链接和话术：</span>
                    <div id="links" style="display: flex;flex-wrap: wrap;align-items: center;flex: 1;"></div>
                </div>
            `)

            const linksBtnConfigsManager = new MD.Manager.ObjectDataListManager({
                key: `global.linksBtnConfigs`,
                items: Array.of(
                    ['邀请上船SOP', `https://shengcaiyoushu01.feishu.cn/wiki/Tl8iwwVKqiNeEDkWHneckDqAncg`, 'link'],
                    ['航海活动首页（航线图）', `https://scys.com/activity/landing?id=${DATA.activityInfo['id']}`, 'link'],
                    ['航海日志首页（打卡页）', `https://scys.com/activity/task?activity_id=${DATA.activityInfo['id']}`, 'link'],
                    ['航海资料库（手册）', `https://scys.com/activity/documents?id=${DATA.activityInfo['id']}`, 'link'],
                    ['航海使用指南', `https://scys.com/docx/QWfQdS9bCoYX7uxv43pccryRn2g`, 'link'],
                    ['退保证金方式', `【退保证金方式】
                    手机端打开链接，在「我的」-「我的航海」，点击到「项目名称」到订单详情，按指示操作即可退款。

                    复制该链接，微信里打开直达—— https://scys.com/mobile/mine/tasks  `, 'text'],
                ).map(a => ({ key: a[0], btnName: a[0], content: a[1].split('\n').map(t => t.trim()).join('\n'), btnType: a[2] })),
                cachingEnabled: { items: true, sortedItemKeys: true, newItemKeys: true }
            })

            var linkBtnsManger
            linksBtnConfigsManager.addManagerListener({
                sortItems: ({ items, sortedItemKeys }) => {
                    linkBtnsManger.sortItems({ sortedItemKeys })
                },
                removeItem: ({ itemKey }) => {
                    linkBtnsManger.removeItem({ itemKey })
                }
            })
            const linkBtns = tabElement.parentElement.parentElement.querySelector('#links')
            const updateLinkBtns = () => {
                linkBtns.innerHTML = ''
                linksBtnConfigsManager.getItems().forEach(({ key, btnName, content, btnType }) => {
                    const btn = document.createElement('div')
                    btn.key = key
                    btn.className = 'md-linkbtn'
                    if (btnName === '双击编辑按钮') {
                        btn.innerHTML = `双击编辑按钮`
                    } else if (btnType === 'img') {
                        btn.innerHTML = `<img src="${content}" style="width: 15px;height: 15px;margin-right: 2.5px;">${btnName}`
                        btn.firstElementChild.addEventListener('mousedown', e => e.stopPropagation())
                    } else if (btnType === 'text') {
                        btn.innerHTML = `📄${btnName}`
                    } else {
                        btn.innerHTML = `🔗${btnName}`
                    }
                    MD.UI.addEventListeners(btn, {
                        onClick: () => {
                            if (btnType === 'text') {
                                MD.UI.copyText(content)
                            } else {
                                window.open(content)
                            }
                        },
                        onDblClick: () => {
                            MD.UI.createPopup((popup) => {
                                popup.innerHTML = `
                                    <div style="font-size: 13px;">
                                        <p>
                                            <span style="margin-right: 10px;">按钮类型：</span>
                                            <input type="radio" id="link" name="btnType" value="link">
                                            <label for="link">链接</label>
                                            <input type="radio" id="text" name="btnType" value="text" style="margin-left:10px">
                                            <label for="text">话术</label>
                                            <input type="radio" id="img" name="btnType" value="img" style="margin-left:10px">
                                            <label for="img">图片</label>
                                        </p>
                                        <p>
                                            <span style="margin-right: 10px;">按钮名字：</span>
                                            <input id="btnName" type="text" style="border: 1px solid #ccc;height: 20px;padding: 0 10px;">
                                        </p>
                                        <p style="display: flex;">
                                            <span style="margin-right: 10px;width: 68px;"><span>按钮内容：</span><br><br><span style="color: #aaa;">文字类型填需要复制的内容，其它类型填链接</span></span>
                                            <textarea id="content" style="width: 300px;height: 100px;padding: 10px;border: solid 1px #ccc;"></textarea>
                                        </p>
                                        <p style="padding: 10px;text-align: center;" id="confirm" class="md-cardbtn">保存按钮设置</p>
                                    </div>
                                `
                                const radios = Array.from(popup.querySelectorAll('[name="btnType"]'))
                                radios.forEach(radio => {
                                    radio.checked = radio.value === btnType
                                })

                                const btnNameInput = popup.querySelector('#btnName')
                                btnNameInput.value = btnName

                                const contentTextarea = popup.querySelector('#content')
                                contentTextarea.value = content || ''

                                popup.querySelector('#confirm').addEventListener('click', () => {
                                    if (!btnNameInput.value || !contentTextarea.value) {
                                        return
                                    }
                                    var item = linksBtnConfigsManager.getItem({ itemKey: key })
                                    item.btnName = btnNameInput.value
                                    item.content = contentTextarea.value
                                    item.btnType = radios.find(radio => radio.checked).value
                                    linksBtnConfigsManager.updateItem({ item })
                                    popup.remove()
                                    updateLinkBtns()
                                });
                            })
                        },
                        onRightClick: () => {
                            const { key: itemKey } = btn
                            MD.UI.createHoverMenu({
                                element: btn,
                                isShowNow: true,
                                buttons: Array.of(
                                    ['前移按钮', () => {
                                        linksBtnConfigsManager.moveItem({ itemKey, direction: -1 })
                                    }, (btn) => {
                                        btn.hidden = linksBtnConfigsManager.getItemIndex(itemKey) === 0
                                    }],
                                    ['后移按钮', () => {
                                        linksBtnConfigsManager.moveItem({ itemKey, direction: 1 })
                                    }, (btn) => {
                                        btn.hidden = linksBtnConfigsManager.getItemIndex(itemKey) === linksBtnConfigsManager.getItems().length - 1
                                    }],
                                    ['添加按钮', () => {
                                        linksBtnConfigsManager.insertItem({
                                            item: {
                                                key: itemKey,
                                                btnName: '双击编辑按钮',
                                                content: '',
                                                btnType: 'text'
                                            }, beforeItemKey: itemKey
                                        })
                                        updateLinkBtns()
                                    }],
                                    ['删除按钮', () => {
                                        linksBtnConfigsManager.removeItem({ itemKey })
                                    }, btn => {
                                        // btn.hidden = !linksBtnConfigsManager.isNewItem({ itemKey })
                                    }]
                                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                            });
                        }
                    })
                    linkBtns.appendChild(btn)
                })
                linkBtnsManger = new MD.Manager.ElementListManager({ key: 'linkBtns', parentElement: linkBtns })
            }
            updateLinkBtns()


            const defBtnRender = (btnName, onBtnClick) => {
                var btn = document.createElement('div')
                btn.id = `md-custombtn-${btnName}`
                btn.className = 'item'
                btn.innerHTML = `<div class="md-custombtn">${btnName}</div>`
                btn.addEventListener('click', () => {
                    onBtnClick(btn.children[0])
                })
                return btn
            }

            Array.of(
                ["切换视图", btnElement => {
                    VIEW.sailorInfoTable.switchDisplayMode()
                }],
                ["小组全家福", btnElement => {
                    MD.UI.createPopup(popup => {
                        const avatasSize = 350
                        popup.style.width = '390px'
                        popup.innerHTML = `
                            <div id="poster" style="
                                background: #006659;
                                width: 390px;
                                height: 520px;
                                display: flex;
                                flex-direction: column;
                                align-items: center;">
                                <img id="logo"  style="width: 100px;margin: 15px 0 18px 0;">
                                <div id="avatars" style="background: transparent;position: relative;height: ${avatasSize}px;width: ${avatasSize}px;"></div>
                                <div contenteditable id="content" style="font-size: 28px;color: #FFD699;text-align: center;margin-top: 15px;resize: none;font-family: cursive;font-weight: 600;letter-spacing: 2px;"></div>
                            </div>
                            <div style="display: flex;width: 100%;margin-top: 10px;box-sizing: border-box;">
                            <div id="downloadBtn" class="md-cardbtn" style="flex:1;padding: 10px;">下载海报</div>
                            <div id="downloadAvatarsBtn" class="md-cardbtn" style="flex:1;padding: 10px;">仅下载头像（DIY海报）</div>
                            </div>
                        `;
                        // <div id="copyBtn" class="md-cardbtn" style="flex:1;padding: 10px;">复制海报</div>
                        // <div id="copyAvatarsBtn" class="md-cardbtn" style="flex:1;padding: 10px;">仅复制头像</div>

                        popup.querySelector('#content').innerHTML = `6月${DATA.activityInfo['航海名称']}航海<div>我们${DATA.volunteerInfo['微信昵称']}组启航啦!</div>`
                        popup.querySelector('#logo').src = document.querySelector('#app > div.topNav > div > a.logo > img').src

                        const avatasContainer = popup.querySelector('#avatars');

                        var avatars = Array.of(
                            DATA.volunteerInfo['星球头像']
                        ).concat(
                            VIEW.sailorInfoTable.bodyRowsManager.getVisibleItems().map(item => {
                                return VIEW.sailorInfoTable.getCellValue({ rowKey: VIEW.sailorInfoTable.bodyRowsManager.getItemKey(item), columnKey: '星球头像' })
                            })
                        ).map(imgUrl => {
                            var img = document.createElement('img')
                            img.src = imgUrl
                            return img
                        })

                        const size = 44;
                        var startIndex = 0
                        var overLen = Math.max(avatars.length - 1 - 35, 0)
                        Array.of(1, 6, 11 + Math.min(2, overLen), 18 + (overLen > 2 ? overLen - 2 : 0)).forEach((len, layoutIndex) => {
                            if (startIndex >= avatars.length) { return }

                            var endIndex = startIndex + len
                            var subAvatars = avatars.slice(startIndex, endIndex)
                            var radius = (size + 5) * layoutIndex
                            const angle = (2 * Math.PI) / subAvatars.length;
                            for (let i = 0; i < subAvatars.length; i++) {
                                const x = radius * Math.cos(i * angle);
                                const y = radius * Math.sin(i * angle);
                                const img = subAvatars[i];
                                img.style = "";
                                img.className = "";
                                img.style.position = "absolute";
                                img.style.width = size - 2 + "px";
                                img.style.height = size + "px";
                                img.style.borderRadius = '50%'
                                img.style.left = (avatasSize - size / 3) / 2 + x - size / Math.PI + "px";
                                img.style.top = (avatasSize - size / 3) / 2 + y - size / Math.PI + "px";
                                avatasContainer.appendChild(img);
                            }
                            startIndex = endIndex
                        })


                        const poster = popup.querySelector('#poster')
                        // popup.querySelector('#copyBtn').addEventListener('click', () => {
                        //     MD.UI.copyImage({ element: poster })
                        // })
                        popup.querySelector('#downloadBtn').addEventListener('click', () => {
                            MD.UI.downloadImage({ element: poster, filename: `11月${DATA.activityInfo['航海名称']}航海丨${DATA.volunteerInfo['微信昵称']}组-全家福海报.png` })
                        })
                        // popup.querySelector('#copyAvatarsBtn').addEventListener('click', () => {
                        //     MD.UI.copyImage({ element: avatasContainer })
                        // })
                        popup.querySelector('#downloadAvatarsBtn').addEventListener('click', () => {
                            MD.UI.downloadImage({ element: avatasContainer, filename: `11月${DATA.activityInfo['航海名称']}航海丨${DATA.volunteerInfo['微信昵称']}组-全家福头像.png` })
                        })
                    })
                }],
//                 ["Day1-21列显示设置", btnElement => {
//                     MD.UI.createHoverMenu({
//                         element: btnElement,
//                         isShowNow: true,
//                         buttons: Array.of(
//                             ['显示日志', () => {
//                                 VIEW.sailorInfoTable.switchRecordDisplay('detail')
//                             }],
//                             ['仅显示是否打卡', () => {
//                                 VIEW.sailorInfoTable.switchRecordDisplay('simple')
//                             }],
//                         ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
//                     });
//                 }],
                // ["话术按钮管理", btnElement => {
                //     MD.UI.createCheckboxPopup({
                //         withoutCheckbox: true,
                //         options: VIEW.sailorInfoTable.copyBtnConfigsManager.getItems().map(({ key, btnName }) => {
                //             return { key, lableText: btnName }
                //         }),
                //         onMoveOption: (index, directionIndex) => {
                //             VIEW.sailorInfoTable.copyBtnConfigsManager.moveItem({ itemKey, direction: directionIndex - index })
                //         },
                //         onGetIndex: (key) => {
                //             return VIEW.sailorInfoTable.copyBtnConfigsManager.getItemIndex(key)
                //         }
                //     })
                // }],
//                 ["表格列管理", btnElement => {
//                     MD.UI.createCheckboxPopup({
//                         options: VIEW.sailorInfoTable.headRowCellsManager._cache.get('sortedItemKeys').map(key => {
//                             return {
//                                 key,
//                                 lableText: VIEW.sailorInfoTable.localValuesManager.getItemValue({ itemKey: `表头&${key}`, valueKey: 'value' })||key,
//                                 checked: !VIEW.sailorInfoTable.headRowCellsManager.isItemHidden({itemKey:key}),
//                                 onCheckChange: (checked) => {
//                                     if (checked) {
//                                         VIEW.sailorInfoTable.showColumn({ columnKey: key })
//                                     } else {
//                                         VIEW.sailorInfoTable.hideColumn({ columnKey: key, force: true })
//                                     }
//                                 }
//                             }
//                         }),
//                         onMoveOption: (key, direction) => {
//                             VIEW.sailorInfoTable.moveColumn({ columnKey:key, direction})
//                         },
//                         onGetIndex: (key) => {
//                             return VIEW.sailorInfoTable.headRowCellsManager._cache.get('sortedItemKeys').indexOf(key)
//                         }

//                     })
//                 }],
                ["脚本设置", btnElement => {
                    MD.UI.createHoverMenu({
                        element: btnElement,
                        isShowNow: true,
                        buttons: Array.of(
                            ['重置话术复制按钮', () => {
                                VIEW.sailorInfoTable.copyBtnConfigsManager.clearCache()
                                window.location.reload()
                            }],
                            ['重置常用链接按钮', () => {
                                linksBtnConfigsManager.clearCache()
                                window.location.reload()
                            }],
                            ['重置表格', () => {
                                VIEW.sailorInfoTable.clearCache()
                                window.location.reload()
                            }],
                            ['重置脚本', () => {
                                MD.Storage.remove()
                                window.location.reload()
                            }],
                        ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                    });
                }],
                ["脚本使用帮助", btnElement => {
                    window.open('https://pskixpl4x5.feishu.cn/docx/WONTdaz9noUTfgxPPGocmgF0n4c')
                }]
            ).map(a => ({ btnRender: a[2] ? a[2] : defBtnRender, btnName: a[0], onBtnClick: a[1] })).forEach(({ btnName, btnRender, onBtnClick }) => tabElement.parentElement.appendChild(btnRender(btnName, onBtnClick)))
        })

    }

    function initStyles() {

        MD.UI.addStyle(`
                    .md-custombtn{
                        background: #36a590;
                        padding: 5px;
                        margin: 2.5px;
                        color: white;
                        position:relative;
                    }
                    .md-custombtn:hover {
                        background: #26816f;  /* 鼠标经过时的背景颜色 */
                        cursor: pointer;  /* 鼠标经过时的光标样式 */
                    }
                    .md-custombtn:active {
                        background: #1c6050;  /* 按钮按下时的背景颜色 */
                        outline: none;  /* 去除按钮按下时的默认轮廓线 */
                    }
                    .md-custombtn-sort{
                        background: #36a590;
                        padding: 4.3px;
                        margin: 2.5px;
                        color: white;
                        display: flex;
                        flex-direction: row;
                    }
                `)

        MD.UI.addStyle(`
                .md-icon-filter{
                    opacity: 0.4;
                    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M128 128a42.666667 42.666667 0 0 1 42.666667-42.666667h682.666666a42.666667 42.666667 0 0 1 42.666667 42.666667v149.333333a42.666667 42.666667 0 0 1-17.578667 34.496L661.333333 469.717333V768a42.666667 42.666667 0 0 1-20.714666 36.586667l-213.333334 128A42.666667 42.666667 0 0 1 362.666667 896V469.717333l-217.088-157.866666A42.666667 42.666667 0 0 1 128 277.333333V128z' fill='%23555' /%3E%3Cpath d='M917.333333 640h-170.666666v-85.333333h170.666666v85.333333zM917.333333 810.666667h-170.666666v-85.333334h170.666666v85.333334z' fill='%23555' /%3E%3C/svg%3E");
                }
                .md-icon-button{
                    opacity: 0.4;
                    width:22px;
                    height:11px;
                    background-repeat: no-repeat;
                    background-position: center;
                }
                .md-icon-button:hover{
                    opacity: 1;
                    cursor: pointer;
                }
                .md-icon-filter-selected{
                    opacity: 1;
                    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M128 128a42.666667 42.666667 0 0 1 42.666667-42.666667h682.666666a42.666667 42.666667 0 0 1 42.666667 42.666667v149.333333a42.666667 42.666667 0 0 1-17.578667 34.496L661.333333 469.717333V768a42.666667 42.666667 0 0 1-20.714666 36.586667l-213.333334 128A42.666667 42.666667 0 0 1 362.666667 896V469.717333l-217.088-157.866666A42.666667 42.666667 0 0 1 128 277.333333V128z' fill='%23555' /%3E%3Cpath d='M917.333333 640h-170.666666v-85.333333h170.666666v85.333333zM917.333333 810.666667h-170.666666v-85.333334h170.666666v85.333334z' fill='%23555' /%3E%3C/svg%3E");
                }
                .md-icon-up{
                    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M19.2 659.2l448-448c25.6-25.6 64-25.6 89.6 0l448 448c38.4 38.4 12.8 108.8-44.8 108.8H64c-57.6 0-83.2-70.4-44.8-108.8z' fill='%23555' /%3E%3C/svg%3E");
                }
                .md-icon-down{
                    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M1004.8 428.8l-448 448c-25.6 25.6-64 25.6-89.6 0l-448-448C-19.2 390.4 6.4 320 64 320h896c57.6 0 83.2 70.4 44.8 108.8z' fill='%23555' /%3E%3C/svg%3E");
                }
                .md-icon-up-selected{
                    opacity: 1;
                    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M19.2 659.2l448-448c25.6-25.6 64-25.6 89.6 0l448 448c38.4 38.4 12.8 108.8-44.8 108.8H64c-57.6 0-83.2-70.4-44.8-108.8z' fill='%23555' /%3E%3C/svg%3E");
                }
                .md-icon-down-selected{
                    opacity: 1;
                    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M1004.8 428.8l-448 448c-25.6 25.6-64 25.6-89.6 0l-448-448C-19.2 390.4 6.4 320 64 320h896c57.6 0 83.2 70.4 44.8 108.8z' fill='%23555' /%3E%3C/svg%3E");
                }
            `)

        MD.UI.addStyle(`
            .md-linkbtn{
                color: #36a590;
                font-size: 12px;
                padding: 2.5px 5px;
                margin: 2.5px;
                cursor: pointer;
                position:relative;
                display:flex;
            }
            .md-linkbtn:hover{
                background:#eee;
            }
        `)

        const cardlist = document.querySelector('.volunteer-list')
        cardlist.style.height = "700px"
        cardlist.style.overflowY = "scroll"
        cardlist.parentElement.style.overflowY = null

    }

})(unsafeWindow);